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

スポンサーリンク

実行時エラーを味方につける!QueryDef実行時の例外ハンドリング設計

Access VBAでシステムを構築していて、最も胃が痛くなる瞬間はいつだろうか?
それは、開発環境では完璧に動いていた動的SQLが、運用フェーズに入ってユーザーの入力値やネットワークの瞬断によって突如としてクラッシュし、無機質な「実行時エラー」のダイアログを叩き出す瞬間ではないか。

特に`QueryDef`オブジェクトを用いた動的SQLの生成と実行において、エラーハンドリングを怠ることは、時限爆弾を抱えて業務アプリを走らせるようなものだ。

今回は、数々の現場でデスクトップデータベースの限界と戦ってきたチーフアーキテクトの視点から、「単にエラーを潰すのではなく、エラーを制御し、味方につけるためのQueryDef例外ハンドリング設計」の極意を授けよう。

—

1. なぜ「DoCmd.RunSQL」や「CurrentDb.Execute」では不十分なのか?

初学者や、場当たり的なコードを書くプログラマは、動的SQLを実行する際に以下のようなコードを書きがちなものだ。

‘ 【アンチパターン】これではエラーの発生源もSQLの中身も追跡できない
CurrentDb.Execute “INSERT INTO T_Log (Action) VALUES (‘” & userText & “‘)”

このアプローチが実務で致命的な理由を挙げておこう。
1. 構文エラーの特定困難: 生成されたSQL文が長大になった際、どの部分でシンタックスエラーが起きたのかデバッグ画面ですら一目で分からない。
2. トランザクション制御の欠如: エラー発生時にどこまで処理が巻き戻された(あるいは中途半端にコミットされた)のか追跡できない。
3. ユーザー体験(UX)の崩壊: VBAの標準エラーメッセージ(「実行時エラー ‘3141’〜」など)がそのまま画面に飛び出し、現場のオペレーターをパニックに陥れる。

実務に耐えうる堅牢なシステムでは、SQLをあらかじめ`QueryDef`としてコンパイルし、その実行時(Execute時)に発生する固有のErrorsコレクションをキャッチする設計が不可欠となる。

—

2. QueryDef実行時エラーの解剖:何が起きているのか?

Access(Jet/ACEエンジン)でクエリを実行する際、エラーは単一の例外として飛んでこないことが多い。ODBC接続を伴うバックエンド(SQL Serverなど)との連携であればなおさらだ。

VBAの`Err`オブジェクトだけでなく、DAOの`DBEngine.Errors`コレクションには、データベースエンジンが発報した詳細なエラーの履歴がスタックされる。真にプロフェッショナルなエラーハンドリングとは、この「エラーの多重構造」を解きほぐし、根本原因をログに焼き付けつつ、ユーザーには優しく翻訳して伝えることを指す。

—

3. 【プロダクションコード】堅牢なQueryDef実行ラッパー関数

それでは、実務の現場でそのままコピペして使える、例外ハンドリングを極めたQueryDef実行のマスターピースを公開しよう。

このコードは、動的SQLの構築、一時QueryDefの安全な破棄(メモリリーク防止)、詳細なエラー解析、そしてトランザクションの整合性を担保する。

Option Compare Database
Option Explicit

‘ =================================================================================
‘ 担当者名: チーフアーキテクト
‘ 概要: 安全なQueryDefの動的生成と例外ハンドリングを行うラッパー関数
‘ =================================================================================
Public Function ExecuteDynamicQuery(ByVal strSQL As String, Optional ByVal lngOptions As Long = dbFailOnError) As Boolean

Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim qdfName As String
Dim errLoop As DAO.Error
Dim i As Long

‘ 一意の一時クエリ名を生成(衝突を防ぐ)
qdfName = “~TempQry_” & Format(Now, “hhmmssnn”) & “_” & Int((9999 – 1000 + 1) Rnd + 1000)

Set db = CurrentDb()

On Error GoTo ErrorHandler

‘ トランザクションの開始(必要に応じて)
db.BeginTrans

‘ 1. QueryDefオブジェクトの作成とSQLの設定
‘ ※ここでSQLの構文解析が行われるため、シンタックスエラーはここで捕捉される
Set qdf = db.CreateQueryDef(qdfName, strSQL)

‘ 2. クエリの実行
‘ dbFailOnErrorを指定することで、途中でエラーがあればロールバック可能な状態にする
qdf.Execute lngOptions

‘ 正常終了時はコミット
db.CommitTrans
ExecuteDynamicQuery = True
GoTo CleanUp

ErrorHandler:
‘ トランザクション中のエラーであればロールバック
On Error Resume Next
db.Rollback
On Error GoTo 0

‘ — 【極限の知見】DAOエラーコレクションの詳細解析 —
Dim errDetail As String
errDetail = “=== QueryDef Execution Error ===” & vbCrLf & _
“Error Number: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description & vbCrLf & _
“SQL Statement:” & vbCrLf & strSQL & vbCrLf & _
“— Database Engine Errors —” & vbCrLf

‘ DAO特有の複数エラーをすべて回収する
If DBEngine.Errors.Count > 0 Then
For i = 0 To DBEngine.Errors.Count – 1
errDetail = errDetail & ” [Error ” & i & “] Number: ” & DBEngine.Errors(i).Number & _
” / Description: ” & DBEngine.Errors(i).Description & vbCrLf
Next i
End If

‘ TODO: ここでerrDetailをアプリケーションログテーブルやテキストファイルに出力する
‘ Call WriteErrorLog(errDetail)

‘ ユーザーへのスマートな通知
MsgBox “データの更新処理中にエラーが発生しました。” & vbCrLf & _
“システム管理者に以下の情報をお伝えください。” & vbCrLf & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“詳細: ” & Err.Description, _
vbCritical, “処理失敗”

ExecuteDynamicQuery = False

CleanUp:
‘ 3. オブジェクトのクリーンアップ(メモリリークおよびシステムカタログの肥大化を防ぐ)
On Error Resume Next
If Not qdf Is Nothing Then
db.QueryDefs.Delete qdfName
Set qdf = Nothing
End If
Set db = Nothing
On Error GoTo 0

End Function

—

4. この設計が「プロフェッショナル」たる理由

上記のコードには、単なるエラー処理を超えた、Access VBAの挙動を知り尽くした者ならではの設計思想が組み込まれている。

① 一時QueryDefの動的削除と命名規則

Accessで `CurrentDb.CreateQueryDef` を行うと、そのデータベースのシステムカタログ(MSysObjects)にクエリが物理的に書き込まれる。これを適切に削除し忘れると、アプリが肥大化し、最終的にデータベースが破損する原因になる。
さらに、マルチユーザー環境や連続実行で名前が衝突しないよう、ミリ秒単位のタイムスタンプと乱数を組み合わせた一意のプレフィックス(`~TempQry_…`)を付与して安全性を高めている。

② `DBEngine.Errors` の完全走査

通常の `Err.Description` だけでは、ODBC経由でSQL Serverや外部データベースを叩いた際のエラー(例:外部キー制約違反やタイムアウト)の「真の原因」が隠されてしまうことがある。DAOの `Errors` コレクションをループで回すことで、データベースエンジン層から返されたすべてのエラーメッセージを漏らさずキャッチできる。

③ トランザクションの確実なロールバック

`dbFailOnError` を指定して実行する場合、途中でコケたらそれまでの変更を一切データベースに残してはならない。`db.BeginTrans` と `db.Rollback` をエラーハンドラ内に組み込むことで、データの整合性(ACID特性)を強烈に担保している。

—

5. 運用時の注意点:ファイル共有とバックエンド連携

もしこの仕組みを「フロントエンド・バックエンド分離構成」(Accessのmdb/accdbを分割し、共有サーバーに置いたバックエンドに接続する構成)で運用する場合、以下の罠に注意してほしい。

  • ネットワーク瞬断によるハングアップ:

Wi-Fi環境や不安定なVPN経由でバックエンドにアクセスしている場合、`qdf.Execute` の瞬間にタイムアウトが発生することがある。これに対処するためには、事前に `DBEngine.LoginTimeout` や接続文字列のタイムアウト設定を適切にチューニングしておくこと。

  • 同時実行制御(排他制御):

他のユーザーが同じレコードをロックしている状態でクエリを実行すると、エラー番号 `3260`(リソースがロックされています)が発生する。この特定のエラー番号をハンドラ内で個別にキャッチし、「現在他のユーザーがこのデータを使用しています。しばらく待ってから再度実行してください」という専用のメッセージに分岐させると、ユーザーからの問い合わせ激減に直結する。

—

まとめ:エラーを飼いならす者だけがAccessを制す

Access VBAは手軽ゆえに「動けばいいや」という雑なコードが量産されがちだ。しかし、業務の根幹を支えるツールに成長した瞬間、その「雑さ」は必ず開発者自身の首を絞めることになる。

今回紹介したQueryDefの例外ハンドリング設計は、エラーをただ隠すものではない。
「エラーが起きたときに、システムが自らを守り、原因を雄弁に語り、ユーザーを迷子にさせないための防壁」である。

この堅牢なパターンをあなたのプロジェクトに組み込み、ワンランク上の安定稼働を実現してほしい。

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