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

スポンサーリンク

Access VBAを掌握する極限の知見:DAOでテーブルの「データ型」を完全制圧し、移行事故をゼロにする実務ツール設計

開発現場で最も恐ろしい瞬間の一つは、大規模なデータ移行や外部システム連携の直前に「型の不一致」でプロセスが全ロストすることだ。
Access(DAO)において、テーブル定義をハードコーディングに頼って管理するなど、地雷原を目隠しで歩くようなものである。

「このテーブルの特定のデータ型を持つフィールドだけを動的に抽出したい」
「移行先のスキーマと完全に一致しているか、プログラム側で一瞬でバリデーションしたい」

今回は、DAO(Data Access Objects)の `Field.Type` プロパティを極限まで理解し、実務の現場で即座に使える堅牢なフィールド抽出・型判定ツールを構築する。
単なるリファレンスの引き写しではない、オブジェクトのライフサイクルとメモリ効率まで考慮したプロダクションコードを授けよう。

—

1. なぜ「型の判定」で多くの開発者が挫折するのか?

初心者がやりがちな最大の過ちは、`Field.Type` が返す値をそのまま人間が読める文字列(”Text”や”Long”など)と比較しようとすることだ。

DAOの `Field.Type` プロパティは、実際には `DataTypeEnum` 定数(数値) を返す。
例えば、テキスト型(短)は `dbText`(値は 10)、長整数型は `dbLong`(値は 4)といった具合だ。さらに厄介なことに、AccessのバージョンやODBC接続テーブルかローカルテーブルかによって、返される定数の解釈に微妙な揺れが生じることがある。

これを「なんとなく」のIf文で書き散らすと、以下のような保守性ゼロのスパゲッティコードが完成する。

‘ 【悪例】絶対にやってはいけないハードコーディング
If fld.Type = 10 or fld.Type = 12 then ‘ マジックナンバーの乱用
‘ 処理…
End If

プロのアーキテクトであれば、定数の意味をカプセル化し、型安全かつ拡張性のあるイミュータブルな判定ロジックを組むべきである。

—

2. 堅牢な設計の要件定義

今回作成する実務ツールは、以下の要件を満たすものとする。

1. ゼロ・ハードコーディング: マジックナンバーを排除し、DAOの組み込み定数(`dbText`, `dbLong` 等)を正確に扱う。
2. メモリリークの完全防止: DAOのオブジェクト(`Database`, `TableDef`, `Field`)は、参照が終わったら即座にメモリから解放する。
3. イミディエイトウィンドウへの美しい出力: 移行チェックが一目でわかるよう、対象フィールドを構造化してログ出力する。

—

3. 【コピペ即稼働】テーブル定義解析・型抽出プロダクションコード

以下のコードを標準モジュールに貼り付けて実行してほしい。
指定したテーブル内の全フィールドをスキャンし、指定したデータ型(今回は例として「テキスト型(`dbText`)」)を持つフィールドのみを完璧に抽出・リストアップする。

Option Explicit
Option Compare Database

‘ =========================================================================
‘ 模範コード:指定テーブルの特定データ型フィールドを完全抽出するプロシージャ
‘ =========================================================================
Public Sub ExtractFieldsByTypeDemo()
‘ 定数の定義(今回は例として「テキスト型 (dbText)」をターゲットにする)
Const TARGET_DATA_TYPE As Integer = dbText
Const TARGET_TABLE_NAME As String = “M_商品マスター” ‘ 実際のテーブル名に変更してください

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

‘ エラーハンドリングの標準装備
On Error GoTo ErrorHandler

‘ カレントデータベースの参照を取得(Newは使わず、CurrentDbを使うのがDAOの鉄則)
Set db = CurrentDb()

‘ テーブルが存在するかどうかの簡易チェック
If Not TableExists(db, TARGET_TABLE_NAME) Then
MsgBox “指定されたテーブル ‘” & TARGET_TABLE_NAME & “‘ が存在しません。”, vbCritical, “致命的エラー”
GoTo Cleanup
End If

‘ TableDefオブジェクトの取得
Set tdf = db.TableDefs(TARGET_TABLE_NAME)

Debug.Print “====================================================”
Debug.Print ” テーブル名: ” & tdf.Name
Debug.Print ” 抽出対象型: ” & GetDataTypeName(TARGET_DATA_TYPE)
Debug.Print “====================================================”

matchCount = 0

‘ 【重要】コレクションの走査にはFor Eachを使用し、インデックスアクセスによるO(N^2)の劣化を防ぐ
For Each fld In tdf.Fields
‘ 型の一致判定
If fld.Type = TARGET_DATA_TYPE Then
matchCount = matchCount + 1
Debug.Print ” [HIT!] フィールド名: ” & fld.Name & _
” | 型: ” & GetDataTypeName(fld.Type) & _
” | サイズ: ” & fld.Size
End If
Next fld

Debug.Print “—————————————————-”
Debug.Print ” 判定完了: 一致したフィールド数 = ” & matchCount & “件”
Debug.Print “====================================================”

Cleanup:
‘ ———————————————————————
‘ オブジェクトのライフサイクル管理:必ず逆順かつ明示的に解放する
‘ ———————————————————————
Set fld = Nothing
Set tdf = Nothing
If Not db Is Nothing Then
db.Close
Set db = Nothing
End If
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “システムエラー”
Resume Cleanup
End Sub

‘ =========================================================================
‘ 補助関数 1: 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: GettDataTypeName = “通貨 (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 dbAttachment: GetDataTypeName = “添付ファイル (Attachment)”
Case Else: GetDataTypeName = “その他 / 未定義 (” & dataType & “)”
End Select
End Function

‘ =========================================================================
‘ 補助関数 2: テーブルの存在確認
‘ =========================================================================
Private Function TableExists(ByRef db As DAO.Database, ByVal tableName As String) As Boolean
Dim tdf As DAO.TableDef
Dim exists As Boolean

exists = False
On Error Resume Next
Set tdf = db.TableDefs(tableName)
If Err.Number = 0 Then exists = True
On Error GoTo 0

TableExists = exists
End Function

—

4. チーフアーキテクトが解説するコードの急所

このコードが「単なる動くコード」ではなく「プロダクション品質」である所以を解説する。

① `CurrentDb()` の正しい作法とメモリ管理

Access VBAでは、`CurrentDb` 関数を呼び出すたびに新しいDatabaseオブジェクトのインスタンスがメモリ上に生成される。これを変数に受けて使い回し、処理の最後には必ず `db.Close` と `Set db = Nothing` を行う。
この規律を破ると、Access特有の「リソース不足(Out of memory)」や、バックエンドファイルへのロック解放遅延を引き起こす。

② マジックナンバーの排除(`GetDataTypeName` 関数)

`dbText` や `dbLong` といったDAOの組み込み定数を使用することで、将来のOfficeのアップデートや仕様変更に対してもコードが破綻しなくなる。さらに、ログ出力時に数値ではなく人間が読める文字列(`GetDataTypeName`)に変換しているため、ログの監査性が圧倒的に向上する。

③ 拡張性:複数型の同時抽出への応用

今回のコードでは `Const TARGET_DATA_TYPE` で単一の型を指定しているが、実務では「数値型(LongとIntegerとDouble)をすべて抽出したい」というケースが多いだろう。
その場合は、判定部分を以下のように独立したヘルパー関数にリファクタリングすればよい。

‘ ターゲットの型が含まれているかを判定する例
Private Function IsTargetType(ByVal fldType As Integer) As Boolean
Select Case fldType
Case dbInteger, dbLong, dbDouble
IsTargetType = True
Case Else
IsTargetType = False
End Select
End Function

—

おわりに:道具に振り回されるな、道具を支配しろ

Access VBAは、その手軽さゆえに「動けばいいや」という雑なコードが蔓延しがちだ。しかし、システムが巨大化し、データ移行のフェーズに入った途端、こうした基礎的なスキーマ検証の不在がプロジェクト全体の遅延を招く。

今回紹介したDAOによるテーブル定義の掌握術は、あなたの開発武器庫において間違いなく強力な一撃となるはずだ。
明日からの開発で、場当たり的なコードを駆逐し、気高きアーキテクトとしての仕事を実行してほしい。

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