【実務・中級編】QueryDefの「実行前イベント」を擬似的に再現する:SQL実行ログと実行ユーザーの自動記録 – Access VBA解析バイブル

スポンサーリンク

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環境にこの「実行の監視網」を張り巡らせるんだ。健闘を祈る。

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