【Access VBA極限統御】DAOを支配せよ:全スキーマを走査し「特定データ型」をミリ秒単位で狩猟する方法
レガシーシステムの全貌を把握することほど、保守エンジニアにとって不毛で、かつ不可避な苦行はない。
「このシステムの中に、いつの時代の設計か分からない『日付型(Date/Time)』のフィールドが一体いくつ散らばっているのか?」
「改修の影響範囲を特定したいのに、ドキュメントは信用できない」
GUIのテーブルデザイナを一つずつポチポチと開いて確認するなど、プロフェッショナルのすることではない。Accessの底流にあるデータアクセスエンジン、すなわちDAO(Data Access Objects)の生命線である`TableDefs`と`Fields`コレクションを直接叩き、メモリの破片すら残さずに高速抽出する。それが真のエンジニアリングだ。
今回は、数千のフィールドを持つ巨大なAccessデータベース(ACCDB/MDB)の深淵から、指定したデータ型を持つフィールドだけを寸分違わず狩猟し、イミディエイトウィンドウへ射出する「スキーマハンター・スクリプト」を授けよう。
—
1. アーキテクチャの核心:DAOオブジェクトのライフサイクルと解放の美学
VBAにおける最大の悪習は、暗黙のオブジェクト参照とメモリリークの放置である。
特にDAOの`Database`、`TableDef`、`Field`といったオブジェクトは、COM(Component Object Model)のラッパーであり、明示的に解放(`Set … = Nothing`)しなければ、Accessのプロセス空間にゴーストメモリとして居座り続ける。
今回提示するコードでは、「浅いループでインスタンスを肥大化させない」「参照は必ずスコープの最小単位で破棄する」という鉄則を完全に遵守している。
DAO定数の罠と型安全性の確保
VBAの`Field.Type`プロパティは、返り値として数値(Integer)を返す。しかし、マジックナンバーをコードに直書きするようなコードは、一見してスパゲッティの温床となる。DAOが提供する組み込み定数(`dbDate`, `dbText`など)を正しく理解し、型安全に比較することが第一歩だ。
—
2. 実装コード:全スキーマ走査型フィールド・ハンター
以下のコードを標準モジュールに貼り付け、`SearchFieldsByType`を実行せよ。
ここでは例として「日付/時刻型(`dbDate`)」をターゲットにするが、定数を書き換えるだけで、長整数型(`dbLong`)や通貨型(`dbCurrency`)など、あらゆる型への変貌を遂げる。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 模範解答:DAOスキーマ超高速走査エンジン
‘ 概要: 指定されたDAOデータ型を持つフィールドを全テーブルから検索し列挙する
‘ =========================================================================
Public Sub SearchFieldsByType()
Dim dbs As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
‘ 検索対象のデータ型を指定 (例: dbDate = 8, dbText = 10, dbLong = 4)
Const TARGET_TYPE As Integer = dbDate
Dim targetTypeName As String
targetTypeName = GetDataTypeName(TARGET_TYPE)
‘ 計測開始(パフォーマンスへの執着)
Dim startTime As Double
startTime = Timer
Debug.Print “————————————————–”
Debug.Print ” スキーマ走査開始: ターゲット型 = ” & targetTypeName & ” (” & TARGET_TYPE & “)”
Debug.Print “————————————————–”
On Error GoTo ErrorHandler
‘ カレントデータベースの参照を取得
Set dbs = CurrentDb
Dim matchCount As Long
matchCount = 0
‘ TableDefsコレクションを走査
‘ ※注意: システムテーブル(MSys…)は除外するロジックを挟むのがプロの作法
For Each tdf In dbs.TableDefs
‘ システムテーブルやリンクテーブル(ODBC等)の挙動を制御する場合のガード
If (tdf.Attributes & dbSystemObject) = 0 Then
For Each fld In tdf.Fields
‘ フィールドのデータ型が一致するか判定
If fld.Type = TARGET_TYPE Then
Debug.Print ” [HIT] テーブル名: ” & tdf.Name & ” | フィールド名: ” & fld.Name & ” (サイズ: ” & fld.Size & “)”
matchCount = matchCount + 1
End If
5 Next fld
End If
Next tdf
Debug.Print “————————————————–”
Debug.Print ” 走査完了. ヒット数: ” & matchCount & ” 件 (” & Format(Timer – startTime, “0.000”) & ” 秒)”
Debug.Print “————————————————–”
CleanUp:
‘ 【重要】オブジェクトの明示的解放によるメモリリークの完全阻止
‘ ループ変数だけでなく、親から順に参照を切断する
Set fld = Nothing
Set tdf = Nothing
If Not dbs Is Nothing Then
dbs.Close
Set dbs = Nothing
End If
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “スキーマハンター異常終了”
Resume CleanUp
End Sub
‘ =========================================================================
‘ 補助関数: DAOデータ型コードを人間が読める文字列に変換
‘ =========================================================================
Private Function GetDataTypeName(ByVal dataType As Integer) As String
Select Case dataType
Case dbBoolean: GetDataTypeName = “Yes/No (Boolean)”
Case dbByte: GetDataTypeName = “バイト型 (Byte)”
Case dbInteger: GetDataTypeName = “整数型 (Integer)”
Case dbLong: GetDataTypeName = “長整数型 (Long)”
Case dbCurrency: GetDataTypeName = “通貨型 (Currency)”
Case dbSingle: GetDataTypeName = “単精度浮動小数点型 (Single)”
Case dbDouble: GetDataTypeName = “倍精度浮動小数点型 (Double)”
Case dbDate: GetDataTypeName = “日付/時刻型 (Date)”
Case dbText: GetDataTypeName = “テキスト型 (Text)”
Case dbLongBinary: GetDataTypeName = “OLEオブジェクト (LongBinary)”
Case dbMemo: GetDataTypeName = “メモ型 (Memo)”
Case Else: GetDataTypeName = “その他/独自型 (” & dataType & “)”
End Select
End Function
—
3. シニアエンジニアが仕込む「3つの最適化と罠の回避策」
上記のコードは単なるループ処理ではない。現場の修羅場をくぐり抜けてきたアーキテクトであれば、以下の3点に唸るはずだ。
① システムテーブルの華麗なるスルー
Accessのバックグラウンドでは、`MSysObjects`や`MSysAccessObjects`といったシステムテーブルが蠢いている。これらを愚直に走査しようとすると、権限エラー(Error 3011など)や無駄なオーバーヘッドが発生する。
`If (tdf.Attributes & dbSystemObject) = 0` というビット演算によるフィルタリングこそが、システムをクラッシュさせないための防壁となる。
② イテレータのスコープ汚染を防ぐメモリマネジメント
`For Each` ループ内でオブジェクト変数を使い回す際、VBAのランタイムは内部で隠蔽されたAddRef/Releaseを行っている。処理が複雑化すると、これが原因でCOMコンポーネントの解放漏れが起き、Access自体が突然終了する「原因不明のフリーズ」を引き起こす。
処理の終端における `Set fld = Nothing` および `Set tdf = Nothing` の徹底、そして何よりも `CurrentDb` を毎回呼び出すのではなく、変数 `dbs` にキャッシュして使い回す設計が、ミリ秒単位の高速化をもたらす。
③ 拡張性:ADO(ADOX)との使い分け
今回はテーブル構造の定義(DDL的側面)に特化しているため、純粋なDAOを採用した。もしこれがSQL Serverや外部RDBへの接続文字列動的生成を伴うのであればADOX(Microsoft ADO Extensions for DDL and Security)への移行を検討すべきだが、純粋なJet/ACEエンジン内での処理であれば、DAOの右に出るスピードスターは存在しない。
—
総括:コードは資産ではなく、使い捨てる「刃」であれ
システム管理者が直面する技術的負債の山に対し、GUIのクリックで立ち向かうのは「素手で火中に飛び込む」ようなものだ。
今回提供したVBAスクリプトは、あなたの認知負荷を極限まで下げ、スキーマの構造を瞬時に暴くための「鋭利な刃」となる。
オブジェクトのライフサイクルを慈しみ、メモリの隅々までコントロール下に対象を置く。その技術的矜持を忘れない限り、どんなに老朽化したAccessシステムであっても、あなたの手で完全に統御できる。
