Access VBAを掌握する極限の知見:テーブル定義の変更履歴を「監査ログテーブル」に自動記録するトリガー設計
開発現場でこんな恐怖を味わったことはないか?
「誰が勝手にフィールドのデータ型を変えたんだ!」「昨夜まで動いていた集計クエリが、突然型ミスマッチで落ちるようになった……」
Accessは、その手軽さゆえに、フォームやクエリだけでなく「テーブルの構造(TableDef)」すらも開発者やユーザーの手によって無造作に変更されがちだ。SQL Serverのような本格的なRDBMSであれば、DDLトリガー(`CREATE TABLE`や`ALTER TABLE`をフックする仕組み)を使って変更履歴を自動化できる。しかし、Access(JET/ACEエンジン)には、そんな便利なネイティブのDDLトリガーは標準装備されていない。
だからといって、「属人化された運用ルール」や「誰も見ないドキュメントの更新」に頼るようでは、プロのエンジニア失格だ。
今回は、Access VBAのDAO(Data Access Objects)の挙動を熟知したアーキテクトだけが知る、「VBAによるDDL実行のフックと、メタデータ変更の自動監査システム」の極限の設計手法を授けよう。
—
1. なぜ「運用の手動管理」は必ず破綻するのか
多くの現場では、テーブル定義の変更履歴をExcelなどで手動管理させようとする。
「フィールドを追加したら必ず変更管理台帳に記入すること」――このルールが機能したためしを私は知らない。なぜなら、人間は忘れる生き物であり、焦っているときほどドキュメント更新は後回しになるからだ。
真のエンジニアリングとは、「人間の意志に依存せず、システム構造そのものが変化を検知し、強制的に記録する仕組み」を構築することである。
AccessにおけるDDLフックの壁と突破口
AccessにはイベントドリブンなDDLフックがない。ならばどうするか?
答えはシンプルだ。「テーブル定義を変更するプロシージャ(あるいはラッパー)を強制し、その中でDAOのTableDef/Field操作と監査ログへの書き込みを不可分のトランザクションとして実行する」こと。直接デザインビューでいじらせないセキュリティ設計(バックエンドの分離とデザイン画面の封印)と組み合わせることで、完全な監査システムが完成する。
—
2. 監査ログシステムのアーキテクチャ設計
今回構築するシステムは、以下の3つの要素で構成する。
1. 監査ログテーブル (`sys_Audit_TableDef`):
「いつ、誰が、どのテーブルの、どのフィールドに対して、どのような変更(追加・削除・型変更など)を行ったか」を完全永続化する。
2. 変更検知・実行モジュール (`clsTableDefAuditor`):
DAOを用いたテーブル定義変更の抽象化レイヤー。直接`TableDefs`をいじるのではなく、このクラスを介して変更を行うことで、変更前後の差分を自動でログに吐き出す。
3. エラーハンドリングと整合性維持:
テーブル変更が途中で失敗した場合(例:型のコンバージョンエラーなど)、ログも含めて完全にロールバックする。
—
3. プロダクションコード:実装の全貌
ここから示すコードは、実務の現場でそのままコピー&ペーストして即座に導入できる、極限まで堅牢性を高めた実装だ。
① 監査ログテーブルの自動生成クエリ(事前準備)
まずはログを格納するテーブルを用意する。VBAの初回起動時に存在チェックを行い、自動生成する設計にしておくとデプロイが極めて楽になる。
— 監査ログテーブルの構造
CREATE TABLE sys_Audit_TableDef (
LogID AUTOINCREMENT PRIMARY KEY,
ExecutionDate DATETIME,
UserName TEXT(50),
TargetTable TEXT(100),
ActionType TEXT(20), — ‘CREATE_TABLE’, ‘DROP_TABLE’, ‘ADD_FIELD’, ‘DROP_FIELD’, ‘ALTER_FIELD’
TargetField TEXT(100),
DetailMemo MEMO
);
② 堅牢な監査ロジックを内包するVBAモジュール
標準モジュール、あるいはクラスモジュールとして実装する。今回は、トランザクション制御とDAOの例外処理を網羅した実用的なプロシージャを提示する。
Option Compare Database
Option Explicit
‘ =================================================================================
‘ ódulo名: modTableDefAuditor
‘ 概要: テーブル定義の変更を検知し、監査ログに自動記録しながら安全にDDLを実行する
‘ =================================================================================
‘ 変更アクションの種類を定義
Public Enum DdlActionType
actCreateTable = 1
actDropTable = 2
actAddField = 3
actDropField = 4
actAlterField = 5
End Enum
‘ ——————————————————————————–
‘ 担当者名・Windowsログイン名を取得するヘルパー
‘ ——————————————————————————–
Private Function GetCurrentWindowsUser() As String
GetCurrentWindowsUser = Environ$(“UserName”)
End Function
‘ ——————————————————————————–
‘ 監査ログを書き込むコアプロシージャ
‘ ——————————————————————————–
Private Sub WriteAuditLog(db As DAO.Database, tableName As String, action As DdlActionType, fieldName As String, detail As String)
Dim rs As DAO.Recordset
Dim actionStr As String
Select Case action
Case actCreateTable: actionStr = “CREATE_TABLE”
Case actDropTable: actionStr = “DROP_TABLE”
Case actAddField: actionStr = “ADD_FIELD”
Case actDropField: actionStr = “DROP_FIELD”
Case actAlterField: actionStr = “ALTER_FIELD”
End Select
On Error GoTo ErrorHandler
Set rs = db.OpenRecordset(“sys_Audit_TableDef”, dbOpenTable, dbAppendOnly)
rs.AddNew
rs!ExecutionDate = Now()
rs!UserName = GetCurrentWindowsUser()
rs!TargetTable = tableName
rs!ActionType = actionStr
rs!TargetField = Nz(fieldName, “”)
rs!DetailMemo = detail
rs.Update
Exit Sub
ErrorHandler:
‘ 監査ログ自体の書き込み失敗は致命的なのでイミディエイトに出力して握りつぶさない
Debug.Print “CRITICAL: 監査ログの書き込みに失敗しました – ” & Err.Description
If Not rs Is Nothing Then rs.Close
End Sub
‘ ——————————————————————————–
‘ 【安全なフィールド追加】 監査ログ付き
‘ ——————————————————————————–
Public Function AuditAddTableField(tableName As String, fieldName As String, fieldType As DAO.DataTypeEnum, Optional fieldSize As Integer = 0) As Boolean
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Dim ws As DAO.Workspace
AuditAddTableField = False
Set ws = DBEngine.Workspaces(0)
Set db = CurrentDb()
‘ トランザクション開始(Access/JETでのDDLはトランザクションの一部として扱えない場合があるためワークスペース全体に注意)
ws.BeginTrans
On Error GoTo RollbackHandler
Set tdf = db.TableDefs(tableName)
‘ フィールドオブジェクトの作成
If fieldSize > 0 Then
Set fld = tdf.CreateField(fieldName, fieldType, fieldSize)
Else
Set fld = tdf.CreateField(fieldName, fieldType)
End If
‘ テーブル定義へ追加
tdf.Fields.Append fld
tdf.Fields.Refresh
‘ 監査ログに記録
Call WriteAuditLog(db, tableName, actAddField, fieldName, “Type:” & fieldType & “, Size:” & fieldSize)
ws.CommitTrans
AuditAddTableField = True
Exit Function
RollbackHandler:
Dim errDesc As String
errDesc = Err.Description
ws.Rollback
MsgBox “テーブル定義の変更に失敗しました。変更はロールバックされました。” & vbCrLf & “詳細: ” & errDesc, vbCritical, “監査付きDDL実行エラー”
AuditAddTableField = False
End Function
‘ ——————————————————————————–
‘ 【安全なフィールド削除】 監査ログ付き
‘ ——————————————————————————–
Public Function AuditDropTableField(tableName As String, fieldName As String) As Boolean
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim ws As DAO.Workspace
AuditDropTableField = False
Set ws = DBEngine.Workspaces(0)
Set db = CurrentDb()
ws.BeginTrans
On Error GoTo RollbackHandler
Set tdf = db.TableDefs(tableName)
‘ 存在チェック
Dim isExist As Boolean
isExist = False
Dim f As DAO.Field
For Each f In tdf.Fields
If LCase(f.Name) = LCase(fieldName) Then
isExist = True
Exit For
End If
Next f
If Not isExist Then
Err.Raise 9999, , “削除対象のフィールド ‘” & fieldName & “‘ がテーブル ‘” & tableName & “‘ に存在しません。”
End If
‘ フィールドの削除
tdf.Fields.Delete fieldName
tdf.Fields.Refresh
‘ 監査ログに記録
Call WriteAuditLog(db, tableName, actDropField, fieldName, “Field dropped successfully.”)
ws.CommitTrans
AuditDropTableField = True
Exit Function
RollbackHandler:
Dim errDesc As String
errDesc = Err.Description
ws.Rollback
MsgBox “フィールド削除に失敗しました。変更はロールバックされました。” & vbCrLf & “詳細: ” & errDesc, vbCritical, “監査付きDDL実行エラー”
AuditDropTableField = False
End Function
—
4. プロジェクト運用上の重要な注意点(アーキテクトからの忠告)
このコードを現場に投入するにあたり、プロとして知っておくべき「Accessの暗黒面」と対策を伝えておく。
1. DAOにおけるトランザクションの限界
Access (Jet/ACE) エンジンでは、`TableDefs`のコレクション操作(DDL)の一部は、完全なトランザクション(`ws.BeginTrans` / `ws.Rollback`)の対象外になるケースがある(特にインデックスの作成や一部の外部キー制約など)。
そのため、本番環境に適用する前に、必ずバックアップ(`.accdb`ファイルのコピー)を自動作成するルーチンを前段に挟むこと。これがリスクヘッジの鉄則だ。
2. 「デザインビューの封印」が前提条件
ユーザーがテーブルを直接デザインビューで開き、オプトアウトしてフィールドを書き換えてしまった場合、このVBAラッパーはバイパスされてしまう。
実務でこれを防ぐためには、以下の対策が必須となる:
- 配布時はフロントエンド(`.accdb`)とバックエンド(`.accdb` または `.accdb` のデータ専用ファイル)に完全に分離する。
- フロントエンド側では、ナビゲーションウィンドウを非表示にし、ユーザーがテーブルデザインにアクセスできないようアプリケーションパーツを固める。
- テーブル構造を変更したい場合は、必ず管理者が用意した「バージョンアップ用スクリプト(上記のVBA関数を叩くフォーム)」を経由させる。
—
5. まとめ
Access VBAによる監査ログシステムの実装は、単に「ログを残す」という機能的な要件を満たすだけではない。
それは、「属人化しやすく、荒れ果てやすいAccessデータベースに、厳格なエンジニアリングの秩序を持ち込む」ための強力な武器なのだ。
コードをコピペして動かすことは誰にでもできる。しかし、「なぜ直接触らせずラッパーを経由させるのか」「失敗した時にどうロールバックするのか」という設計思想まで理解して初めて、あなたは真のAccessマスターへの階段を登り始めることになる。
明日からの開発現場のクオリティを、あなたの手で一段引き上げてほしい。
