Access VBAで「テーブル定義変更」を破壊させない:トランザクション設計の極意
Accessでシステムを構築していると、避けて通れないのが「テーブル定義の動的な変更」だ。運用開始後に「カラムを追加したい」「インデックスを張り直したい」といった要件は必ず発生する。
しかし、多くの開発者はここで大きな過ちを犯す。DDL(Data Definition Language)操作を何の保護もなしに実行するのだ。
もし、テーブルの構造変更中にネットワークが瞬断したり、Accessがハングアップしたらどうなるか? データベースは「中途半端な状態(不整合)」で放置され、最悪の場合、ファイル全体が破損する。
本稿では、プロフェッショナルとして守るべき「DDL操作におけるトランザクション制御」の真髄を伝授する。
—
なぜ「DAOのトランザクション」だけでは不十分なのか
まず理解してほしいのは、Access(Jet/ACEエンジン)の`BeginTrans` / `CommitTrans`は、主にDML(データ操作:Insert, Update, Delete)を保護するためのものだ。
残念ながら、`TableDefs`オブジェクトを直接操作するDDL処理の多くは、`CommitTrans`で完全に保護しきれない挙動を示すことがある。特に「テーブルそのものの削除・再作成」や「複雑なリレーションシップの再構築」を行う際、中途半端に構造が変わった状態でロックがかかると、復旧不能なダメージを負う。
極限の設計指針:
1. 「作業用テンポラリテーブル」を経由する(既存テーブルを直接弄らない)
2. エラー発生時は、変更を破棄して「元の状態」を強制復元する
3. DAO.Databaseオブジェクトを適切に再利用し、メモリリークを防ぐ
—
実践:堅牢なDDLトランザクション・テンプレート
以下に、実務でそのまま使える「安全にテーブル構造を変更する」ためのパターンを示す。このコードの肝は、構造変更に失敗した際、確実に旧テーブルを保護する「バックアップ・ロールバック」の思想にある。
‘ —————————————————————–
‘ テーブル定義変更用:安全なトランザクション・テンプレート
‘ —————————————————————–
Public Sub SafeModifyTableStructure()
Dim db As DAO.Database
Dim td As DAO.TableDef
Dim isTransactionStarted As Boolean
Set db = CurrentDb
On Error GoTo ErrorHandler
‘ 1. トランザクション開始
DBEngine.BeginTrans
isTransactionStarted = True
‘ 2. バックアップの作成(失敗時の保険)
‘ 既存テーブルをリネームしてバックアップとするのが最も堅牢
DoCmd.CopyObject , “T_Target_Backup”, acTable, “T_Target”
‘ 3. DDL操作の実行
‘ 例:フィールド追加
db.Execute “ALTER TABLE T_Target ADD COLUMN NewField TEXT(50)”, dbFailOnError
‘ 4. コミット
DBEngine.CommitTrans
isTransactionStarted = False
‘ 成功したらバックアップを削除(不要であれば残しても良い)
DoCmd.DeleteObject acTable, “T_Target_Backup”
MsgBox “構造変更が完了しました。”, vbInformation
Exit Sub
ErrorHandler:
‘ 5. ロールバック処理
If isTransactionStarted Then
DBEngine.Rollback
End If
‘ バックアップから復旧を試みる
If ObjectExists(“T_Target_Backup”) Then
On Error Resume Next
DoCmd.DeleteObject acTable, “T_Target”
DoCmd.Rename “T_Target”, acTable, “T_Target_Backup”
End If
MsgBox “致命的なエラーが発生しました。変更はロールバックされました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“説明: ” & Err.Description, vbCritical
End Sub
‘ 補足:オブジェクト存在チェック関数
Private Function ObjectExists(objName As String) As Boolean
ObjectExists = (DCount(“”, “MSysObjects”, “Name='” & objName & “‘”) > 0)
End Function
—
現場で差がつく3つの視点
1. `dbFailOnError` の徹底
`db.Execute` を使う際、第二引数に `dbFailOnError` を指定していないコードは「論外」だ。これがないと、SQL実行時にエラーが発生してもVBA側で捕捉できず、コードが何事もなかったかのように次へ進んでしまう。業務自動化ツールにおいて、これは「サイレントバグ」の温床となる。
2. リレーションシップの扱い
DDLでテーブルを弄る際、リレーションシップが張られていると「テーブルの変更」そのものが拒絶される。事前に `db.Relations` を走査し、関連するリレーションを削除してから変更を行い、最後に再構築するロジックを組むべきだ。この「掃除」を自動化できるかどうかが、エンジニアの腕の見せ所である。
3. 排他制御を意識せよ
DDL操作はデータベース全体に対して強いロックをかける。共有環境(ネットワーク上の共有フォルダ)で実行する場合、他に開いているユーザーが一人でもいると失敗する。必ず `db.TableDefs.Refresh` を行い、エラーハンドラで「ユーザーが使用中」である旨を明示的に伝えるUIを設計すること。
結びに代えて
テーブル定義の変更は、データベースにとっての「手術」だ。安易に `ALTER TABLE` を乱発するのではなく、常に「失敗したらどう戻すか」という設計思想を持ってほしい。
真の業務自動化エンジニアとは、「コードが動くこと」を喜ぶのではなく、「コードが壊れたときに、いかにシステムを安全な状態へ誘導するか」を設計できる者を指す。このアーキテクチャを理解し、堅牢なシステムを構築してほしい。応援している。
