【実務・中級編】【初心者】特定のデータ型を持つフィールドだけを抽出してリスト化するツール – Access VBA解析バイブル

スポンサーリンク

【Access VBAを掌握する極限の知見】全テーブル・全フィールドの海から「特定の型」だけを瞬時に釣り上げるメタプログラミング手法

開発現場で、こんな絶望的な状況に直面したことはないだろうか。

「システム全体で、一体いくつのテーブルに『日付/時刻型(dbDate)』のフィールドが存在しているのか洗い出してほしい」
「設計書の不整合を確認するために、全テーブルから『長整数型(dbLong)』の外部キー候補をすべてリスト化してくれ」

これを手作業でやろうものなら、何十個、何百個とあるテーブルを開き、デザインビューを1つずつ目視で確認するという、エンジニアとしての尊厳を削ぎ落とされるような不毛な作業が待っている。

しかし、Access VBAのDAO(Data Access Objects)のライフサイクルとメタデータ構造を正しく理解していれば、わずか数秒、数十行のコードでこの全探索を完了させることができる。

今回は、単に動くだけのコードではない。実務の巨大なデータベース(ACCDB)を相手にしてもメモリリークを起こさず、エラーを完璧にハンドリングする、プロダクションクオリティの「特定データ型フィールド抽出ツール」の全貌を授けよう。

1. なぜ「力技(チート)」のクエリではダメなのか?

初学者が陥りやすい罠として、SQLの `INFORMATION_SCHEMA` や、すべてのテーブルを結合した巨大なSELECT文を作ろうとするアプローチがある。

しかし、考えてみてほしい。
テーブルごとにカラム構成がバラバラな状態で、純粋なSQLだけで「全テーブルのフィールドのデータ型をメタレベルで走査する」ことは、AccessのSQL方言(Jet/ACE SQL)の限界を超える。

ここで使うべきなのは、Accessの心臓部である DAO(Data Access Objects) だ。
Accessのデータベース構造(定義情報)は、すべて `TableDefs`、`Fields` というオブジェクトツリー構造としてメモリ上に表現されている。これらを直接叩くことこそが、最もエレガントかつ確実なアプローチなのだ。

2. 実装における3つの極意(バグを防ぐ鉄則)

実務で動く堅牢なツールを作るためには、以下の3点をコードに組み込まなければならない。

1. システムテーブルの除外
`MSys` で始まるシステムテーブルや隠しテーブルにアクセスすると、権限エラーや予期せぬ例外が発生する。これらは最初からスキップするガード節が必須。
2. 適切な定数(`DataTypeEnum`)の指定
フィールドの型判定には、曖昧な文字列ではなく、DAOが提供する正式な定数(例: `dbDate`, `dbLong`, `dbText`)を使用する。
3. オブジェクトの解放(ライフサイクルの管理)
`CurrentDb` を無闇に乱用すると、内部的なオブジェクト参照カウンタが肥大化し、パフォーマンス低下やメモリリークの温床となる。参照は変数に格納し、適切に解放する意識を持つこと。

3. コピペで即戦力!プロダクションコード

以下のコードを標準モジュールに貼り付けて実行してほしい。
指定したデータ型(デフォルトでは「日付/時刻型:`dbDate`」)を持つフィールドをすべて探し出し、イミディエイトウィンドウに美しく一覧出力、さらに確認用の新規一時テーブルとして出力する。

Option Compare Database
Option Explicit

‘ =========================================================================
‘ 開発プロジェクト名 : データベース構造解析ユーティリティ
‘ 処理概要 : 指定したデータ型を持つ全テーブルのフィールドを抽出する
‘ アーキテクト特記事項: 巨大なACCDBでもメモリを圧迫しないDAO参照設計
‘ =========================================================================
Public Sub ExtractFieldsByType()

‘ 1. 検索したいデータ型をここで定義(例: dbDate = 日付/時刻型)
‘ ※他の主な定数: dbLong (長整数型), dbText (テキスト型), dbBoolean (Yes/No型)
Const TARGET_DATA_TYPE As Integer = dbDate
Const TARGET_TYPE_NAME As String = “日付/時刻型”

Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Dim rsLog As DAO.Recordset

Dim matchCount As Long
Dim startTime As Double

startTime = Timer
matchCount = 0

‘ 現在のデータベース参照を取得
Set db = CurrentDb

‘ 【出力先準備】結果を格納する一時テーブルを作成(存在する場合は一度削除)
Call PrepareResultTable(db)

‘ 結果格納用レコードセットを開く
Set rsLog = db.OpenRecordset(“T_FieldSearch_Result”, dbOpenTable)

‘ トランザクション開始(一括書き込みの高速化)
db.BeginTrans

On Error GoTo ErrorHandler

‘ 2. 全テーブル定義を走査
For Each tdf In db.TableDefs

‘ システムテーブル(MSys…)やリンクテーブルを除外するガード節
‘ 先頭が “MSys” または Attributes に dbAttachedTable が含まれるものはスキップ
If (tdf.Attributes & dbSystemObject) = 0 And _
(tdf.Attributes & dbAttachedTable) = 0 Then

‘ テーブル内の全フィールドを走査
For Each fld In tdf.Fields

‘ 3. 指定したデータ型と一致するか判定
If fld.Type = TARGET_DATA_TYPE Then

‘ 一時テーブルにヒット情報を記録
rsLog.AddNew
rsLog(“TableName”).Value = tdf.Name
rsLog(“FieldName”).Value = fld.Name
rsLog(“DataType”).Value = TARGET_TYPE_NAME
rsLog.Update

‘ イミディエイトウィンドウにもリアルタイム出力
Debug.Print “発見: [” & tdf.Name & “] 側の [” & fld.Name & “]”
matchCount = matchCount + 1

End If
Next fld

End If
Next tdf

‘ コミット
db.CommitTrans

‘ 終了メッセージ
MsgBox “解析が完了しました。” & vbCrLf & _
“対象データ型 (” & TARGET_TYPE_NAME & “) のフィールド数: ” & matchCount & ” 件” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒” & vbCrLf & _
“結果はテーブル ‘T_FieldSearch_Result’ に保存されました。”, _
vbInformation, “構造解析完了”

CleanUp:
‘ 4. オブジェクトの厳格な解放(メモリリーク防止)
On Error Resume Next
If Not rsLog Is Nothing Then rsLog.Close: Set rsLog = Nothing
If Not db Is Nothing Then Set db = Nothing
Exit Sub

ErrorHandler:
‘ 異常発生時のロールバック
db.Rollback
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, _
vbCritical, “致命的エラー”
Resume CleanUp

End Sub

‘ =========================================================================
‘ 内部プロシージャ: 結果格納用の一時テーブルを初期化する
‘ =========================================================================
Private Sub PrepareResultTable(ByRef db As DAO.Database)
Dim tdfNew As DAO.TableDef

‘ 既存の同名テーブルがあれば削除
On Error Resume Next
db.TableDefs(“T_FieldSearch_Result”).Delete
On Error GoTo 0

‘ 新規テーブル定義の作成
Set tdfNew = db.CreateTableDef(“T_FieldSearch_Result”)

With tdfNew
.Fields.Append .CreateField(“TableName”, dbText, 255)
.Fields.Append .CreateField(“FieldName”, dbText, 255)
.Fields.Append .CreateField(“DataType”, dbText, 50)
End With

db.TableDefs.Append tdfNew
Set tdfNew = Nothing
End Sub

4. コードのアーキテクチャ解説:プロがこだわるポイント

① `Attributes` によるスマートなフィルタリング

`For Each tdf In db.TableDefs` で回すと、Accessが内部で管理している `MSysAccessObjects` や `MSysQueries` といったシステムテーブルまで引っかかってしまう。これらを触るとエラーになるか、不要なノイズデータになる。
コード内の `(tdf.Attributes & dbSystemObject) = 0` というビット演算によって、純粋なユーザー定義テーブルだけを美しくフィルタリングしている。さらに、外部パススルーやリンクテーブル(`dbAttachedTable`)も除外対象として安全性を高めている。

② トランザクションによる高速化

結果を保存する際、1件ずつディスクに書き込むのではなく、`db.BeginTrans` と `db.CommitTrans` で囲むことにより、書き込み処理をメモリ上で一括処理し、I/Oのボトルネックを解消している。数千フィールドある巨大データベースであっても、体感できるレベルで高速に動作する。

③ 徹底的なメモリ管理

VBA開発において、オブジェクト変数の解放(`Set xxx = Nothing`)を怠ることは、C言語で `free` を忘れるのと同じ罪深いことだ。特にAccessのDAOは、セッションが残ったままメモリ上にオブジェクトを保持し続ける性質があるため、`CleanUp` ラベルを用意し、いかなる例外ルートを通ろうとも確実にリソースが解放される設計にしている。

5. おわりに:ここから先の実務応用へ

このスクリプトをベースにすれば、例えば以下のような実用的な社内ツールへと簡単に拡張できる。

  • 変更管理ツール:特定のフィールド名(例: `UpdateDate` など)が、全テーブルに正しくれ漏れなく実装されているかを監査する自動テストスクリプト。
  • マイグレーション支援:旧システムのデータ型設計を解析し、新システム(SQL Server等)へ移行する際の見積もり自動化ツール。

「コードを書く前に、まず構造を知る」。
このメタプログラミングの視点を持てば、あなたのAccess開発における生産性は次元の違う領域へとシフトするはずだ。ぜひ、現場の武器として役立ててほしい。

タイトルとURLをコピーしました