ようこそ。私のラボへ。
Access VBAを弄り回している開発者は多いが、その多くは「GUIでできることを自動化している」レベルに留まっている。だが、真のアーキテクトが直面するのは、数百のテーブル、数千のフィールドが複雑に絡み合うエンタープライズ規模のデータ構造だ。
特に、「リレーションシップの自動構築」。これは単に`Relations.Append`を呼び出す作業ではない。参照整合性の制約、データ型の不一致、そして何より恐ろしい「循環参照(あるいは複雑な依存関係による構築順序の破綻)」をどう制御するかが、プロフェッショナルとアマチュアの分かれ目だ。
今日は、場当たり的なコードを捨て、どんなに複雑なスキーマでも確実に構築し切る「依存関係解析アルゴリズム」を組み込んだ、極限の自動構築ロジックを伝授しよう。
—
1. なぜ、あなたのリレーションシップ構築は失敗するのか
通常のコードでは、「テーブルAからテーブルBへリレーションを張る」という命令を並列に書くだけだ。しかし、これには3つの致命的な欠陥がある。
1. 依存性の逆転: 親テーブル(主キー側)が存在しない、あるいは主キーが定義されていない状態で子テーブル(外部キー側)から参照しようとしてエラーになる。
2. 型不整合の放置: Long型とInteger型を繋ごうとするような初歩的なミスを、実行時まで検知できない。
3. クリーンアップの欠如: 既存のリレーションが残っている状態で再構築しようとし、重複エラーで停止する。
これを解決するには、「トポロジカルソート(順序付け)」の概念を簡略化した、「リトライ・キュー方式」を採用するのが最も賢明だ。
—
2. 堅牢なリレーションシップ構築のアーキテクチャ
単に順序を守るだけでなく、以下の3ステップを自動化する。
1. 既存関係の全破壊: 既存の制約を一度リセットし、まっさらな状態で構築を開始する。
2. 定義オブジェクトの抽象化: テーブル名、フィールド名、属性(連鎖更新・削除など)を構造体またはクラスで管理する。
3. 再帰的キュー処理: 構築に失敗したリレーションを末尾に回し、前提条件が整うまでリトライを繰り返す。
—
3. 極限のプロダクションコード:Dependency-Aware Builder
以下のコードは、単なるサンプルではない。私が実務で大規模移行ツールを構築する際に採用している、「自己修復型構築エンジン」のコアロジックだ。
‘ — モジュールレベルの宣言 —
Option Explicit
‘ リレーションシップの定義を保持するカスタム型
Private Type RelationDefinition
RelName As String
PrimaryTable As String
ForeignTable As String
PrimaryField As String
ForeignField As String
Attributes As Long ‘ dbRelationUpdateCascade 等
End Type
”’
”’
Public Sub Architect_BuildRelationships()
Dim db As DAO.Database
Set db = CurrentDb
On Error GoTo ErrorHandler
‘ 1. 既存のリレーションシップをすべて削除(クリーンアップ)
‘ これを行わずに構築するのは、泥沼の上に家を建てるようなものだ
Call ClearAllRelationships(db)
‘ 2. 構築キューの初期化
Dim queue As Collection
Set queue = New Collection
‘ ここで定義を追加していく(本来は定義テーブルやJSONから読み込むべきだ)
‘ 例: 注文データと顧客データの紐付け
AddRelationDefinition queue, “Rel_Cust_Ord”, “T_Customer”, “T_Order”, “CustomerID”, “CustomerID”, dbRelationUpdateCascade
AddRelationDefinition queue, “Rel_Ord_Det”, “T_Order”, “T_OrderDetail”, “OrderID”, “OrderID”, dbRelationDeleteCascade
‘ 複雑な依存関係(例: T_OrderがT_Customerに依存している)も、このエンジンなら順序を気にせず放り込める
‘ 3. 構築エンジンの実行
Dim retryCount As Integer
Dim successCount As Integer
Dim totalDef As Integer: totalDef = queue.Count
Do While queue.Count > 0
Dim def As RelationDefinition
‘ コレクションから先頭を取り出す
‘ ※VBAのCollectionは削除するとインデックスが詰まるため、常に1を参照
Dim currentRel As Variant: currentRel = queue(1)
queue.Remove 1
If TryCreateRelation(db, currentRel) Then
successCount = successCount + 1
retryCount = 0 ‘ 成功したらリトライカウンタをリセット
Else
‘ 失敗(親テーブルが未完成など)した場合は、キューの末尾へ回す
queue.Add currentRel
retryCount = retryCount + 1
End If
‘ 無限ループ防止(全アイテム数以上のリトライが連続したら、定義自体が不正)
If retryCount > queue.Count Then
Err.Raise 1001, “Architect_Engine”, “循環参照または不正な定義を検知しました。構築を中断します。”
End If
Loop
MsgBox “リレーションシップ構築完了: ” & successCount & ” 件処理されました。”, vbInformation, “Success”
Exit Sub
ErrorHandler:
MsgBox “致命的エラー: ” & Err.Description, vbCritical, “Error”
End Sub
‘ — 補助関数群(これこそがノウハウの塊だ) —
Private Sub ClearAllRelationships(db As DAO.Database)
Dim i As Integer
‘ 後ろから削除するのが定石(インデックスの変化を防ぐ)
For i = db.Relations.Count – 1 To 0 Step -1
‘ システムリレーションシップ(MSys…)以外を削除
If Not (db.Relations(i).Attributes And dbRelationInherited) Then
db.Relations.Delete db.Relations(i).Name
End If
Next i
End Sub
Private Sub AddRelationDefinition(ByRef q As Collection, n As String, pt As String, ft As String, pf As String, ff As String, attr As Long)
Dim d As RelationDefinition
d.RelName = n: d.PrimaryTable = pt: d.ForeignTable = ft: d.PrimaryField = pf: d.ForeignField = ff: d.Attributes = attr
q.Add d
End Sub
Private Function TryCreateRelation(db As DAO.Database, def As Variant) As Boolean
On Error Resume Next
Dim rel As DAO.Relation
Dim fld As DAO.Field
‘ リレーションオブジェクトの生成
Set rel = db.CreateRelation(def.RelName, def.PrimaryTable, def.ForeignTable, def.Attributes)
‘ フィールドの紐付け
‘ ※ここで親テーブルに主キーがない、あるいはテーブルが存在しないとエラーになる
Set fld = rel.CreateField(def.PrimaryField)
fld.ForeignName = def.ForeignField
rel.Fields.Append fld
‘ データベースへ反映
db.Relations.Append rel
If Err.Number = 0 Then
TryCreateRelation = True
Else
‘ デバッグ用にエラーを吐かせても良いが、基本はリトライに回すため静かに閉じる
Debug.Print “Retry queued: ” & def.RelName & ” Reason: ” & Err.Description
TryCreateRelation = False
End If
On Error GoTo 0
End Function
—
4. 現場を掌握するアーキテクトの視点
リトライ・キュー方式の優位性
なぜ「トポロジカルソート(依存順の計算)」を厳密に行わず、この「リトライ方式」を採用するのか。それは、Access DAOの実装の揺らぎを許容するためだ。
インデックスの作成タイミングや、リンクテーブルの接続状況など、計算上の順序だけでは解決できない実行時の「不都合」がAccessには多々ある。キューを回す方式は、それらすべてを「準備が整うまで待つ」という単純かつ強力なロジックで解決する。
`Attributes` プロパティの重み
コード内の `dbRelationUpdateCascade`(連鎖更新)や `dbRelationDeleteCascade`(連鎖削除)は、ビジネスロジックの根幹だ。これを手動で設定させる運用は必ず事故を招く。VBAで強制することで、データ整合性のガバナンスを開発者が握ることができる。
循環参照への警告
もし、AがBを、BがCを、CがAを参照するような設計があれば、このエンジンは無限ループ(リトライオーバー)としてそれを検知し、停止する。これはデータベース設計そのものが破綻しているという「設計へのフィードバック」として機能する。
—
結論
リレーションシップの制御をマスターすることは、AccessというRDBMSの心臓部を支配することに等しい。
今回紹介した「自己修復型構築エンジン」をあなたのツールに組み込めば、テーブルが増えるたびにリレーションの設定順序に頭を悩ませる時間は永遠に消失するだろう。コードは単に動けばいいのではない。「どのような状況下でも、正解に辿り着くロジック」こそが、プロフェッショナルの成果物だ。
このロジックを手に、目の前の複雑なスパゲッティ・スキーマを鮮やかに解きほぐしてほしい。
