【実務・中級編】DAO.Field.Attributesを解析し、自動採番(AutoNumber)フィールドをVBAで特定する – Access VBA解析バイブル

スポンサーリンク

【Access VBA極限解説】DAO.Field.Attributesを完全掌握し、自動採番フィールドを動的に看破する

開発現場でよくある悪夢を思い出してほしい。
「外部システムから受け取る数百万件のCSVデータを、Accessのテーブルに高速インポートする汎用ツールを作ってくれ」
そして、要件定義書を開くとこう書いてある。
「テーブル構造や列の増減は今後も頻繁に発生しますが、ツール側はそれを自動で吸収してください」

素人が書いたコードなら、ここで固定のフィールド名やインデックスを指定して撃沈する。しかし、プロのアーキテクトは違う。我々はDAO(Data Access Objects)の深部、メタデータの領域へと潜り込み、テーブルのDNAをプログラムに読み込ませるのだ。

今回は、DAOの最も強力かつ見落とされがちなプロパティ `Field.Attributes` を解剖し、「自動採番(AutoNumber)フィールド」を100%の確度で動的に特定し、インポート時の致命的なエラーを防ぐ堅牢なエンジニアリングを伝授する。

なぜ「自動採番」の判定で多くの開発者が散るのか?

Access VBAでテーブル構造を舐める時、多くの初級者は `CurrentDb.TableDefs(“T_Master”).Fields` をループさせる。ここまでは良い。しかし、問題はその先だ。

「このフィールドは自動採番だから、INSERT文の列リストから除外しなければならない」
そう判断する際、君はどんなコードを書くだろうか? フィールド名が “ID” かどうかで判定しているとしたら、今すぐそのキーボードを置いてほしい。実務の現場では、ユーザーは平気で `ID` ではなく `管理番号` や `seq_key` という名前で自動採番フィールドを作る。

フィールドの性質を正しく知る唯一の手段、それが `DAO.Field.Attributes` プロパティ である。

Attributesの正体:ビットマスクの海

`Attributes` プロパティは、単一の数値を返すものではない。複数の属性(主キー、更新不可、自動採番、固定長など)がビット演算(Bitwise)によって1つの長整数(Long)に圧縮されている。

つまり、`Field.Attributes = 17` のような値をそのまま比較しても意味がない。その数値の中に「自動採番フラグ(Bit)」が立っているかどうかを、論理積(And演算子)を用いて正確にマスクし、抽出しなければならないのだ。

核心:`dbAutoIncrField` ビットを暴く

DAOにおいて、自動採番フィールドを示す定数は `dbAutoIncrField` である。その値は `16`(`&H10`)だ。

あるフィールドが自動採番であるかを判定するロジックの核心は、以下の数式に集約される。

If (fld.Attributes And dbAutoIncrField) <> 0 Then
‘ このフィールドは自動採番(AutoNumber)である!
End If

このシンプルな条件分岐こそが、ハードコーディングの呪縛からあなたを解放する鍵となる。

プロダクションコード:自動採番を避けて通る汎用インポーター

机上の空論はここまでだ。ここからは、実際の業務でそのまま使える、堅牢性とパフォーマンスを極限まで高めたプロダクションコードを公開する。

このコードは、指定したテーブルの構造を動的に解析し、「自動採番フィールドをINSERT対象から自動的に除外する」汎用インポート処理のコアモジュールである。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 処理名: 汎用テーブル構造解析 & インポート前処理エンジンの模範実装
‘ 備考 : DAO.Field.Attributesを解析し、自動採番フィールドを完全に制御する
‘ ==============================================================================
Public Sub AnalyzeAndPrepareImport(ByVal targetTableName As String)
On Error GoTo ErrorHandler

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

‘ CurrentDbは呼び出しごとにオーバーヘッドが発生するため、変数にキャッシュする
Set db = CurrentDb()

‘ テーブルの存在確認とTableDefの取得
If Not TableExists(db, targetTableName) Then
MsgBox “対象テーブル ‘” & targetTableName & “‘ が存在しません。”, vbCritical, “致命的エラー”
Exit Sub
End If

Set tdf = db.TableDefs(targetTableName)

Debug.Print “=== テーブル解析開始: ” & targetTableName & ” ===”

‘ 全フィールドを走査
For Each fld In tdf.Fields
Debug.Print “—————————————-”
Debug.Print “フィールド名: ” & fld.Name
Debug.Print “データ型(Type): ” & GetDataTypeName(fld.Type)
Debug.Print “サイズ(Size): ” & fld.Size
Debug.Print “属性値(Attributes): ” & fld.Attributes

‘ 1. 自動採番(AutoNumber)の判定
If (fld.Attributes And dbAutoIncrField) <> 0 Then
Debug.Print ” -> 【判定】自動採番フィールドです(INSERT時は除外必須)”
‘ TODO: インポート時のSQL構築時にこのフィールドを除外するリストに追加する

‘ 2. 必須入力(Required)の判定
ElseIf (fld.Attributes And dbUpdatableField) = 0 Then
Debug.Print ” -> 【判定】更新不可(システム管理)フィールドの可能性があります”

Else
Debug.Print ” -> 【判定】通常のデータ入力可能フィールド”
End If

‘ 3. 主キー(Primary Key)の判定はIndexesコレクションから行う(オマケ知見)
If IsFieldPrimaryKey(tdf, fld.Name) Then
Debug.Print ” -> 【情報】このフィールドは主キーに含まれています”
End If
Next fld

Debug.Print “=== テーブル解析完了 ===”

Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End Sub

‘ — ヘルパー関数群 —

‘ テーブル存在確認
Private Function TableExists(ByVal db As DAO.Database, ByVal tableName As String) As Boolean
Dim tdf As DAO.TableDef
On Error Resume Next
Set tdf = db.TableDefs(tableName)
TableExists = (Err.Number = 0)
On Error GoTo 0
End Function

‘ 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

‘ フィールドが主キーの一部であるかを判定する関数
Private Function IsFieldPrimaryKey(ByVal tdf As DAO.TableDef, ByVal fieldName As String) As Boolean
Dim idx As DAO.Index
Dim fld As DAO.Field

For Each idx In tdf.Indexes
‘ PrimaryKeyプロパティがTrueのインデックスを走査
If idx.Primary Then
For Each fld In idx.Fields
If fld.Name = fieldName Then
IsFieldPrimaryKey = True
Exit Function
End If
Next fld
End If
Next idx
IsFieldPrimaryKey = False
End Function

アーキテクトからの実践的アドバイス:実務での罠と対策

このコードを現場に投入するにあたり、プロとしていくつかの重要な「注意点」を付け加えておく。

1. `CurrentDb` の乱用に気をつけろ

ループの内側で `CurrentDb.TableDefs` を呼び出すような愚行は厳禁だ。`CurrentDb` は呼び出されるたびに新しいデータベースオブジェクトのインスタンスをメモリ上に生成するため、激しいパフォーマンス低下とリソースリークの原因になる。上記コードのように、一度変数に格納(`Set db = CurrentDb()`)して使い回すのが鉄則である。

2. 外部データベース(SQL Server等)をリンクしている場合の挙動

今回のDAOによる `Attributes` 解析は、あくまでAccessのローカルテーブル(Jet/ACEエンジン)において極めて正確に動作する。
もしターゲットがODBCリンクテーブル(SQL Serverなど)である場合、Access側から見た `Attributes` は正確な自動採番フラグを返さないことがある(SQL Server側のIDENTITY属性はDAO経由では正しくマッピングされないケースがある)。
もしバックエンドがRDBなら、DAOではなくADOや直接SQL Serverのメタデータを叩く設計に切り替える判断力を持たなければならない。

結論

Access VBAにおける真の強さとは、動的なメタデータ操作をマスターすることにある。
ハードコードされたVBAは、要件変更のたびに修正が必要な「負債」でしかない。しかし、今回解説した `DAO.Field.Attributes` とビット演算を組み合わせたアプローチを取り入れれば、どんなに構造が変わり続けるテーブルであっても、絶対に破綻しない強靭なデータ連携エンジンを構築できる。

あなたの書くコードを、ただ動くだけの玩具から、プロフェッショナルなシステムへと昇華させてほしい。

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