Access VBAを掌握せよ:QueryDefの「実行前イベント」を擬似構築し、堅牢な監査ログを実装する
Access開発の現場で、もっとも「追跡不能」で恐ろしいバグの温床は何だと思うか? それは、「誰が、いつ、どのSQLを走らせたか分からないクエリ」だ。
QueryDefは非常に強力だが、ネイティブに「実行前イベント」を持っていない。そのため、多くの初級者は個別のプロシージャ内に `CurrentDb.Execute` を乱雑に記述し、結果としてログの欠落やメンテナンス不能なスパゲッティコードを生み出す。
今日伝授するのは、QueryDefの実行を「ラップ(包み込む)」し、実行の瞬間に強制的に監査ログを刻むためのアーキテクチャだ。これを導入するだけで、君のシステムは「管理されるべきツール」へと進化する。
—
1. なぜ「直接実行」を捨て、ラップすべきなのか
VBAで `DoCmd.OpenQuery` や `CurrentDb.Execute` を直書きするのは、エンジニアの怠慢だ。
なぜなら、「実行のコンテキスト(誰が、何を、なぜ)」を記録する場所がないからだ。
我々が目指すべきは、QueryDefを直接呼び出すのではなく、「実行エンジン(Wrapper)」を通す設計である。これにより、以下のメリットが確定する。
- 完全なトレーサビリティ: 実行者と時刻、対象クエリ名、SQL文の履歴が必ずログDBに残る。
- 例外処理の標準化: 実行エラーが発生した際、そのSQLを即座にログとして特定できる。
- 保守性の向上: ログ記録の仕様変更(例えば、ログ先をファイルから外部DBへ変えるなど)が、1箇所を修正するだけで完了する。
—
2. 堅牢な監査ログ実装アーキテクチャ
まずは、ログを記録するためのテーブル(`tbl_ExecutionLog`)を用意せよ。フィールドは最低限これだけは必須だ。
- `LogID` (オートナンバー)
- `ExecutionTime` (日付/時刻)
- `UserName` (環境変数から取得)
- `QueryName` (実行したQueryDef名)
- `SQLStatement` (実行したSQL本文)
実装コード:QueryDef実行のWrapper関数
これを標準モジュールに配置し、今後はすべてのクエリ実行をこの関数経由で行うように徹底する。
Option Compare Database
Option Explicit
‘ — QueryDef実行のWrapper関数 —
‘ 引数: queryName (実行するクエリ名), params (動的パラメータを適用する場合のDictionary)
Public Sub ExecuteManagedQuery(ByVal queryName As String, Optional ByVal params As Object = Nothing)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim sql As String
Set db = CurrentDb
Set qdf = db.QueryDefs(queryName)
‘ パラメータがある場合は適用
If Not params Is Nothing Then
Dim key As Variant
For Each key In params.Keys
qdf.Parameters(key) = params(key)
Next key
End If
‘ SQLを取得
sql = qdf.sql
‘ 【重要】実行前に監査ログを記録
Call WriteAuditLog(queryName, sql)
‘ 実行(トランザクション制御が必要な場合はここを拡張せよ)
On Error GoTo Err_Handle
qdf.Execute dbFailOnError
Set qdf = Nothing
Set db = Nothing
Exit Sub
Err_Handle:
MsgBox “クエリ実行エラー: ” & Err.Description, vbCritical
‘ ここでログDBにエラー内容を書き込む処理を入れれば完璧だ
End Sub
‘ — ログ記録用プライベートプロシージャ —
Private Sub WriteAuditLog(ByVal qName As String, ByVal sqlText As String)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset(“tbl_ExecutionLog”, dbOpenDynaset)
With rs
.AddNew
!ExecutionTime = Now()
!UserName = Environ(“USERNAME”) ‘ Windowsログイン名を取得
!QueryName = qName
!SQLStatement = sqlText
.Update
.Close
End With
Set rs = Nothing
Set db = Nothing
End Sub
—
3. 実践:動的SQLとパラメータークエリの運用
このWrapperを使う際、動的SQLを生成して `QueryDef.SQL` プロパティを書き換える手法は推奨しない。それはクエリの実行プランを無効化し、パフォーマンスを悪化させるからだ。
「あらかじめパラメータクエリとして定義しておき、実行時に値を注入する」
これが王道だ。もし複雑な動的SQLが必要な場合でも、クエリ定義をテンプレートとして保持し、上記 `params` 引数(Scripting.Dictionaryなど)を使って値を流し込むように設計しろ。これにより、SQLインジェクションのリスクを排除しつつ、実行ログの整合性を担保できる。
—
4. チーフアーキテクトからの助言
- パフォーマンスの罠: ログ記録を同期的に行うと、大量のクエリを実行する際にボトルネックになることがある。その場合は、ログテーブルへの書き込みを非同期、あるいはバッファリングする設計を検討せよ。
- 隠蔽の徹底: ユーザーに直接 `QueryDef` を触らせてはならない。DAOの `QueryDefs` コレクションへの直接アクセスを制限(あるいはモジュール単位でカプセル化)し、必ずこのWrapperを通す文化をチームに醸成せよ。
コードは単に動けばいいというものではない。「後から見た時に、誰が何をしようとしたのか」がすべて語られる状態こそが、プロフェッショナルが作る業務システムだ。
さあ、今すぐ君のAccess環境にこの「実行の監視網」を張り巡らせるんだ。健闘を祈る。
