【上級】Accessスキーマ監査:誰も気づかぬ「テーブル定義変更」をVBAで完全に掌握する
Access開発の現場において、最も恐ろしいのは「知らない間に誰かがテーブル構造を変えていること」だ。
「動いていたはずのクエリが突然エラーを吐く」「インポート仕様が壊れた」……その原因の8割は、開発者や現場担当者による軽率なフィールド変更にある。
今回は、Accessのデータベースエンジン(DAO)の深淵に触れ、「いつ、誰が、どのテーブルの何をいじったか」を自動検知し、ログとして刻み込む究極のスキーマ監査モジュールを伝授する。
—
なぜ「DAO.TableDef」の監視が必要なのか
Accessのテーブル構造は `TableDefs` コレクションを通じてアクセス可能だ。しかし、このプロパティは「現在の状態」を示すだけであり、変更履歴は持たない。
多くのエンジニアは「修正履歴表」を手動で書くという不毛な作業に時間を費やす。だが、プロの仕事はそうではない。スキーマ変更をイベントとしてトラップし、DB内部のメタデータとして恒久的に記録する。これが堅牢なシステムを構築する者の責務だ。
—
実装の戦略:ContainerとDocumentによる「変更検知」
Accessには、テーブルやクエリの情報を管理するための「システムオブジェクト」が存在する。
`MSysObjects` を直接いじるのは御法度だが、`DAO.Container` オブジェクトを通じてスキーマ情報を監視することは公式に認められた「作法」である。
今回は、システム起動時にテーブル定義のハッシュ(または最終更新日時)を比較し、変更があれば即座に監査ログテーブルへ書き込むアーキテクチャを採用する。
—
プロダクションコード:Schema Auditor
以下のコードを標準モジュールに配置し、システムの起動時に `AuditTableDefinitions` を呼び出す構成にする。
Option Compare Database
Option Explicit
‘ — 監査ログ用定数 —
Private Const LOG_TABLE_NAME As String = “T_SchemaAuditLog”
”’
”’
Public Sub AuditTableDefinitions()
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim currentHash As String
Set db = CurrentDb
‘ システムテーブルを除外してループ処理
For Each tdf In db.TableDefs
If Left(tdf.Name, 4) <> “MSys” And Left(tdf.Name, 1) <> “~” Then
‘ フィールド数と最終更新日時をキーとして構造変化を検知
currentHash = tdf.Name & “_” & tdf.Fields.Count & “_” & tdf.LastUpdated
‘ ここで前回のハッシュ値を保持するローカルテーブルと照合
‘ 変更があればログテーブルに書き込む
If IsSchemaChanged(tdf.Name, currentHash) Then
LogSchemaChange tdf.Name, “構造変更が検知されました”
End If
End If
Next tdf
End Sub
Private Sub LogSchemaChange(tableName As String, changeNote As String)
Dim sql As String
‘ 環境変数やWindowsログインユーザー名を取得して記録する
Dim userName As String
userName = Environ(“USERNAME”)
sql = “INSERT INTO ” & LOG_TABLE_NAME & ” (TableName, ChangeDate, ChangedBy, ChangeNote) ” & _
“VALUES (‘” & tableName & “‘, Now(), ‘” & userName & “‘, ‘” & changeNote & “‘)”
CurrentDb.Execute sql, dbFailOnError
End Sub
Private Function IsSchemaChanged(tableName As String, currentHash As String) As Boolean
‘ ※ここに「前回のハッシュ値を保持するマスタテーブル」との照合ロジックを実装
‘ 変更があればTrueを返し、マスタを更新する
IsSchemaChanged = True ‘ サンプルにつき常にTrue
End Function
—
運用上の重要な注意点
1. パフォーマンスへの配慮
`TableDefs` は非常に強力だが、テーブル数が多いと起動時に負荷がかかる。監視対象を特定の実務テーブルに絞り込むか、あるいは特定のIDを持つテーブルのみをチェックするフィルタリングを必ず実装すること。
2. 排他制御の罠
`CurrentDb.Execute` を使う際は、必ず `dbFailOnError` を指定せよ。ログ記録に失敗した際、その事実を無視してシステムを続行するのはエンジニアとして失格だ。
3. メタデータの保存場所
監査ログを「変更対象のDB」に置くと、循環参照やロック競合のリスクがある。可能であれば、ログ専用のバックエンドDB(BE)を切り出し、リンクテーブル経由で記録するのが最も安全である。
—
最後に:なぜこの設計なのか
多くの初心者は「変更があった時にダイアログを出す」ようなコードを書くが、それは現場を混乱させるだけだ。
「背後で黙々と、しかし確実に記録を残す」。
これが、障害発生時に「なぜこのテーブルの型が変更されたのか?」という問いに対して、秒速で答えを出すための布石となる。
このモジュールをあなたのシステムに組み込むだけで、保守コストは劇的に下がり、何よりあなたのエンジニアとしての信頼性が跳ね上がるはずだ。
次は、このログテーブルをトリガーにして、変更前のテーブル定義をバックアップとしてCSV出力する「自動スナップショット機能」の実装に挑んでみてほしい。Accessの限界を突破するのは、いつだってこうした泥臭い作り込みだ。
