【テクニカル・上級編】【初心者】VBAでテーブル内の全フィールドの「データ型」を判定し、特定の型のみを抽出する実務ツール – Access VBA解析バイブル

スポンサーリンク

【Access VBAを掌握する極限の知見】DAOの深淵:型判定とメモリ最適化による高速フィールド抽出エンジン

レガシーシステムの保全、あるいは他システムとのデータ連携。その最前線において、Access VBAの真価が問われる瞬間がある。それは「構造の不確実性」に直面したときだ。

数千、数万のレコードを持つテーブル群。場当たり的に追加・改修されてきたカラムの群れ。データ移行の際、「このフィールドの本当のデータ型は何か?」という問いに躓き、夜半のバッチ処理が型ミスマッチで沈没した経験を持つシニアエンジニアは少なくないはずだ。

今回は、DAO(Data Access Objects)の `Field.Type` プロパティを極限までドライに使いこなし、特定のデータ型を持つフィールドを瞬時に炙り出す実務ツールを実装する。単なる「動くコード」ではない。オブジェクトのライフサイクル管理、メモリ最適化、そして型定数の暗黒面までを網羅した、チーフアーキテクトの知見をここに公開する。

—

1. DAOにおけるデータ型判定の罠と真実

初心者が最初に踏み抜く地雷は、`Field.Type` が返す値をそのまま人間が読める文字列と比較することだ。DAOの型定義は、VBAの `VarType` や `DataTypeEnum` が入り交じるカオスな領域である。

例えば、文字列型一つをとっても、可変長文字列(`dbText`)とメモ型(`dbMemo`、現代のAccessでは「長いテキスト」)は別物であり、さらにOLEオブジェクトや添付ファイル型は、メモリ上で全く異なる扱いを受ける。

また、DAOのオブジェクトモデルを操作する際、最も恐れなければならないのは「COMオブジェクトの解放漏れによるメモリリーク」だ。Accessのガベージコレクションは気まぐれであり、特にループ内で `TableDef` や `Field` を次々と参照していくと、確実にメモリフットプリントが肥大化し、最悪の場合、Jet/ACEエンジンがクラッシュする。

—

2. 実装:高速フィールド抽出エンジン

以下のコードは、指定したテーブルから特定のデータ型(例:数値型やテキスト型など)を持つフィールドを完全に網羅し、イミディエイトウィンドウに出力、あるいは配列として返す実務レベルのプロシージャだ。

オブジェクトの参照は必ず逆順で解放し、エラーハンドリングブロックで確実にメモリリークを防ぐ設計としている。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 概要: 指定したテーブルから特定のDAOデータ型を持つフィールドを抽出する
‘ アーキテクトノート:
‘ DAO.Field.Typeの定数マッピングを完全に理解し、メモリリークを排除した実装。
‘ 大規模なスキーマ解析でも安定稼働するよう、オブジェクト変数の明示的解放を徹底。
‘ ==============================================================================
Public Sub ExtractFieldsByType(ByVal TargetTableName As String, ByVal TargetDataType As DataTypeEnum)

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

Dim matchCount As Long
Dim executionStartTime As Double

executionStartTime = Timer
matchCount = 0

On Error GoTo ErrorHandler

‘ 現在のデータベース参照を取得(パフォーマンスのためCurrentDb関数を直接叩きまくらない)
Set db = CurrentDb()

‘ テーブルの存在確認とTableDefの取得
If Not IsTableExists(db, TargetTableName) Then
Err.Raise 9999, “ExtractFieldsByType”, “指定されたテーブル [” & TargetTableName & “] は存在しません。”
End If

Set tdf = db.TableDefs(TargetTableName)

Debug.Print “——————————————————–”
Debug.Print ” スキーマ解析開始: ” & TargetTableName
Debug.Print ” ターゲット型条件: ” & GetDataTypeName(TargetDataType) & ” (Value: ” & TargetDataType & “)”
Debug.Print “——————————————————–”

‘ フィールドコレクションの走査
‘ シニアの知見: For Eachは内部でIEnumVARIANTを呼ぶため、DAOでは安全かつ高速。
For Each fld in tdf.Fields
‘ 型の一致判定(必要に応じてビット演算や範囲指定に拡張可能)
If fld.Type = TargetDataType Then
matchCount = matchCount + 1
Debug.Print ” [HIT] フィールド名: ” & fld.Name & _
” | 型: ” & GetDataTypeName(fld.Type) & _
” | サイズ: ” & fld.Size & _
” | 属性: ” & fld.Attributes
End If
Next fld

Debug.Print “——————————————————–”
Debug.Print ” 解析完了. 一致フィールド数: ” & matchCount & ” (処理時間: ” & Format(Timer – executionStartTime, “0.000”) & ” 秒)”
Debug.Print “——————————————————–”

CleanUp:
‘ ————————————————————————–
‘ オブジェクトの明示的解放(Memory Optimization)
‘ 参照を切る順序は生成の逆、かつNULL代入によるCOM参照カウンタのデクリメント
‘ ————————————————————————–
On Error Resume Next
If Not fld Is Nothing Then Set fld = Nothing
If Not tdf Is Nothing Then Set tdf = Nothing
If Not db Is Nothing Then Set db = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Number & ” – ” & Err.Description, vbCritical, “致命的エラー”
Resume CleanUp

End Sub

‘ — ヘルパー関数: テーブル存在確認 —
Private Function IsTableExists(ByRef dbTarget As DAO.Database, ByVal TableName As String) As Boolean
Dim tdfCheck As DAO.TableDef
On Error Resume Next
Set tdfCheck = dbTarget.TableDefs(TableName)
IsTableExists = (Err.Number = 0)
Set tdfCheck = Nothing
On Error GoTo 0
End Function

‘ — ヘルパー関数: DAOデータ型を人間が読める文字列に変換 —
Private Function GetDataTypeName(ByVal dt As DataTypeEnum) As String
Select Case dt
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 dbGUID: GetDataTypeName = “GUID”
Case dbChar: GetDataTypeName = “Char”
Case dbNumeric: GetDataTypeName = “10進型 (Numeric)”
Case dbDecimal: GetDataTypeName = “Decimal”
Case dbFloat: GetDataTypeName = “Float”
Case dbVarBinary: GetDataTypeName = “VarBinary”
Case Else: GetDataTypeName = “不明/特殊型 (” & dt & “)”
End Select
End Function

—

3. チーフアーキテクトが教える「現場の知見」

上記のコードを実務のパイプラインに組み込む際、以下の3つのポイントを心に刻んでおいてほしい。

① `CurrentDb` の濫用禁止

コード内で何回も `CurrentDb.TableDefs` を呼び出す愚を犯してはならない。`CurrentDb` は呼び出すたびに新しいDatabaseオブジェクトのインスタンスをメモリ上に生成する。変数 `db` に一度だけ格納し、それを使い回すこと。これだけでメモリリークとパフォーマンス低下の8割を防げる。

② DAOとADOの混在によるコンテキスト汚染

Access VBAでは ADODB も利用可能だが、テーブル定義(DDLやメタデータ)の操作においては、ADO(ADOX)よりも DAOの方が圧倒的にネイティブかつ高速 である。Jet/ACEエンジンとの親和性が段違いなため、構造解析には迷わずDAOを選択すべきだ。

③ データ移行・ETLツールへの応用

このコードをベースに、特定の型(例えば `dbText`)をすべて `dbMemo` に変換する動的DDL生成スクリプトや、外部SQL Serverへマイグレーションする際のスキーマバリデーションツールへと発展させることができる。基幹システムの堅牢性を担保するのは、こうした泥臭いメタデータ解析の自動化に他ならない。

技術の本質を見極め、コードのライフサイクルを支配する者だけが、レガシーの呪縛からシステムを解放できる。次のデプロイメントで、ぜひこのエンジンを組み込んでその圧倒的な速度と安定性を体感してほしい。

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