実行時エラーを味方につける!QueryDef実行時の例外ハンドリング設計
Access VBAにおける最大の悪夢は、「実行時まで表面化しないSQLの歪み」と、「消えないCOMオブジェクトによるメモリリーク」である。
特に、UI層から切り離された動的SQLの生成、あるいは外部データベース(SQL Server等)とのODBC接続を伴う`QueryDef`の実行において、エラーハンドリングを怠ることは、自らシステムに爆弾を抱え込むと同義だ。
「エラーが出たら `MsgBox Err.Description` で終わり」――そんな幼稚なコードを書く時代は終わった。
本稿では、シニアアーキテクトが現場で実践している、`QueryDef`実行時の例外ハンドリングの極限の知見を授ける。
—
1. なぜ `DoCmd.RunSQL` や手抜きクエリ実行が地獄を生むのか
動的SQLを実行する際、多くの初学者は `CurrentDb.Execute` や `DoCmd.RunSQL` を直書きする。しかし、これには致命的な欠点がある。
1. 実行計画のキャッシュが効かない(あるいは汚染される)
2. エラー発生時のコンテキスト(どのパラメータで落ちたか)が消え去る
3. DAOのオブジェクト参照が背後でリークし、Accessが徐々に肥大化・不安定化する
特にマルチユーザ環境や、数百件のレコードをループ処理するバッチ処理において、一瞬のタイムアウトやデッドロック、型不一致エラーによってアプリケーション全体が沈黙する事故は後を絶たない。
真のプロフェッショナルは、一時的な `QueryDef` オブジェクトを明示的に生成・破棄し、そのライフサイクルを完全に掌握することで、あらゆる異常をハンドリングする。
—
2. 【実装パターン】例外を制御し尽くす堅牢なQueryDef実行エンジン
以下のコードは、動的SQLの組み立て、パラメータのバインド、そして発生し得るあらゆるDAO/ODBCエラーをトラップして構造化ログとユーザーフレンドリーな通知を両立させる、実戦投入レベルのテンプレートである。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 模範的QueryDef実行プロシージャ(例外ハンドリング&メモリ最適化完全版)
‘ =========================================================================
Public Sub ExecuteDynamicQuerySafe(ByVal strSQL As String, Optional ByRef prms As Scripting.Dictionary)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim errLoop As DAO.Error
Dim isTransactionStarted As Boolean
isTransactionStarted = False
‘ 1. データベース参照の取得(CurrentDbの乱用禁止。必ず変数に受けて解放する)
Set db = CurrentDb()
On Error GoTo Error_Handler
‘ 2. トランザクションの開始(必要に応じて)
‘ db.BeginTrans
‘ isTransactionStarted = True
‘ 3. 一時QueryDefの生成(名前を固定せずユニークな一時名を与えるか、名無しで作成)
‘ ※QueryDefを永続保存しないことでシステムコンテナの肥大化を防ぐ
Set qdf = db.CreateQueryDef(“”, strSQL)
‘ 4. パラメータの動的バインド(SQLインジェクション対策および型安全性の確保)
If Not prms Is Nothing Then
Dim varKey As Variant
For Each varKey In prms.Keys
If qdf.Parameters.Item(varKey).Type Then
qdf.Parameters(varKey).Value = prms(varKey)
End If
Next varKey
End If
‘ 5. クエリの実行(結果を返さないアクションクエリを想定)
‘ dbFailOnErrorを指定し、途中で失敗した場合は即座に例外を発生させる
qdf.Execute dbFailOnError
‘ if isTransactionStarted Then db.CommitTrans
Clean_Up:
‘ 6. オブジェクトの明示的解放(逆順が鉄則)
On Error Resume Next
If Not qdf Is Nothing Then
qdf.Close
Set qdf = Nothing
End If
If Not db Is Nothing Then
Set db = Nothing
End If
Exit Sub
Error_Handler:
‘ 7. DAO固有の詳細なエラー解析
Dim strErrDetail As String
strErrDetail = “DAO Error Occurred.” & vbCrLf
If db.Errors.Count > 0 Then
For Each errLoop In db.Errors
strErrDetail = strErrDetail & _
” Number: ” & errLoop.Number & vbCrLf & _
” Source: ” & errLoop.Source & vbCrLf & _
” Description: ” & errLoop.Description & vbCrLf
Next errLoop
Else
strErrDetail = strErrDetail & _
” Number: ” & Err.Number & vbCrLf & _
” Description: ” & Err.Description & vbCrLf
End If
‘ トランザクション中のエラーであればロールバック
If isTransactionStarted Then
On Error Resume Next
db.Rollback
strErrDetail = strErrDetail & “[System] Transaction Rolled Back.” & vbCrLf
End If
‘ 8. ログ出力およびユーザーへの通知(ここではイミディエイト出力とメッセージボックス)
Debug.Print strErrDetail
MsgBox “データの更新処理中にエラーが発生しました。” & vbCrLf & _
“システム管理者に以下の情報をお伝えください。” & vbCrLf & vbCrLf & _
Err.Description, vbCritical, “致命的な実行時エラー”
Resume Clean_Up
End Sub
—
3. チーフアーキテクトが解説する「3つの極意」
上記のコードには、単なる「エラー処理の記述」を超えた、Access/DAOの内部構造に踏み込んだ設計思想が宿っている。
① `db.Errors` コレクションの完全走査
VBA標準の `Err` オブジェクトだけでは、ODBC経由(SQL ServerやOracleなど)で発生したデータベース側の詳細なエラーコードやメッセージ(SQLStateなど)が抜け落ちる。
DAOの `db.Errors` コレクションをループさせることで、データベースエンジンが発したすべての警告・エラーの履歴をキャッチできる。これがトラブルシューティングのスピードを劇的に変える。
② `CurrentDb()` のスコープ管理とメモリ解放
`CurrentDb` メソッドは呼び出すたびに新しい `Database` オブジェクトのインスタンスをメモリ上に生成する。
これを `CurrentDb.Execute …` のように直接叩くと、参照を失ったオブジェクトがメモリ上に残り続け、Access特有の「リソース不足(Out of Memory)」エラーを引き起こす主原因となる。
必ずローカル変数 `db` に受け、処理の終端で `Set db = Nothing` を明示的に実行し、VBAのガベージコレクション頼みにしないこと。
③ 一時QueryDef (`””` の指定) によるコンテナ汚染防止
`db.CreateQueryDef(“”, strSQL)` の第一引数に空文字列 `””` を渡すことで、システムカタログ(MSysObjects)に永続保存されない「一時QueryDef」を生成できる。
これを怠り、適当な名前をつけて永続クエリとして保存し続けると、アプリケーションのmdb/accdbファイルがゴミデータで肥大化し、パフォーマンスが急激に劣化していく。使い捨てのQueryDefは使い捨てるのが鉄則だ。
—
4. レガシー環境・システム間連携における実戦的アドバイス
基幹系システムや外部API、あるいは別サーバーのSQL ServerとODBCリンクしている環境では、ネットワークの瞬断やタイムアウトが日常茶飯事である。
こうした環境では、上記の `ExecuteDynamicQuerySafe` に対し、リトライ機構(指数バックオフなど)をラップさせることが求められる。
特にタイムアウトエラー(DAOエラー番号で特定)を検知した場合のみ、数秒のウェイトを挟んで最大3回まで自動リトライする設計を組み込むことで、ユーザーに「システムが落ちた」と感じさせないレジリエンスの高いアーキテクチャが完成する。
エラーを隠蔽するのではなく、エラーを構造化し、リソースを完全に制御下に対置すること。
それこそが、レガシーとモダンが混在する現場を制するエンジニアの条件である。
