【テクニカル・上級編】実行時エラーを味方につける!QueryDef実行時の例外ハンドリング設計 – Access VBA解析バイブル

スポンサーリンク

実行時エラーを味方につける!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回まで自動リトライする設計を組み込むことで、ユーザーに「システムが落ちた」と感じさせないレジリエンスの高いアーキテクチャが完成する。

エラーを隠蔽するのではなく、エラーを構造化し、リソースを完全に制御下に対置すること。
それこそが、レガシーとモダンが混在する現場を制するエンジニアの条件である。

タイトルとURLをコピーしました