Access VBAを掌握せよ:DAO.Recordsetの動的型判定で実現する「壊れない」データ転送エンジン
業務システムを構築していて、最も「心が折れる瞬間」はいつだろうか。それは、テーブル構造のわずかな変更やデータ型の不一致によって、丹精込めて書いたインポート処理が `実行時エラー 13: 型が一致しません` を吐いて止まった時だ。
「とりあえず値を代入する」という甘い実装は、プロフェッショナルの仕事ではない。今回は、DAO.Recordsetの`Type`プロパティを徹底的に使い倒し、どんなフィールド構成にも動的に追従する「堅牢なデータ転送エンジン」の設計思想を伝授する。
—
1. なぜ「力技の代入」は地雷なのか
初心者は、`rsTarget!FieldA = rsSource!FieldA` のように、フィールド名を直書きし、型を決め打ちする。だが、これは以下の理由から致命的だ。
- 保守性の欠如: フィールドが1つ増えるたびにコードを書き換える必要がある。
- 型不整合の爆弾: Null値の混入や、数値型への文字列混入など、実行時にしか判明しないエラーでシステムが停止する。
- 拡張性の皆無: 異なるテーブル構造を扱うたびに、同じような処理をコピペして肥大化させる。
我々が目指すべきは、「スキーマ(構造)をメタデータとして扱い、DAOが持つ型情報をプログラム自身に判断させる」アプローチだ。
—
2. 実装の極意:DAO.Field.Typeの真価
DAOの`Field`オブジェクトには、その列が何者であるかを示す`Type`プロパティが存在する。これを利用し、転送元(Source)から転送先(Target)へ、型安全性を担保しながらデータを流し込むロジックを組む。
汎用データ転送プロシージャ
このコードは、テーブル同士の完全なデータ移行、あるいは一時テーブルからのバッチ処理にそのまま組み込める設計だ。
‘ ———————————————————————-
‘ 機能: DAO Recordsetの型を判定し、安全に値を転送する汎用プロシージャ
‘ ———————————————————————-
Public Sub TransferRecord(ByRef rsSource As DAO.Recordset, ByRef rsTarget As DAO.Recordset)
Dim fldSource As DAO.Field
Dim fldTarget As DAO.Field
On Error GoTo ErrorHandler
For Each fldSource In rsSource.Fields
‘ 転送先に同名のフィールドが存在する場合のみ処理
If FieldExists(rsTarget, fldSource.Name) Then
Set fldTarget = rsTarget.Fields(fldSource.Name)
‘ Nullチェック:Nullの場合は代入をスキップ(またはデフォルト値処理)
If IsNull(fldSource.Value) Then
fldTarget.Value = Null
Else
‘ ここで型判定を行い、必要に応じて変換をかける
‘ Typeプロパティによる分岐で、型不一致エラーを未然に防ぐ
Select Case fldSource.Type
Case dbText, dbMemo, dbChar
fldTarget.Value = Left(fldSource.Value, fldTarget.Size) ‘ 文字列切り詰め防止
Case dbDouble, dbSingle, dbDecimal, dbCurrency
fldTarget.Value = CDbl(fldSource.Value)
Case dbLong, dbInteger
fldTarget.Value = CLng(fldSource.Value)
Case Else
fldTarget.Value = fldSource.Value
End Select
End If
End If
Next fldSource
Exit Sub
ErrorHandler:
Debug.Print “Error in Field: ” & fldSource.Name & ” – ” & Err.Description
Resume Next ‘ 致命的でない限り、次のフィールド処理へ続行する設計
End Sub
‘ 補助関数:フィールド存在確認
Private Function FieldExists(rs As DAO.Recordset, fieldName As String) As Boolean
On Error Resume Next
FieldExists = Not IsNull(rs.Fields(fieldName))
If Err.Number <> 0 Then FieldExists = False
End Function
—
3. この設計が「現場」で生き残る理由
① メタデータ駆動設計
このコードは、フィールド名が何であれ、`rsTarget`に同じ名前があれば処理を行う。テーブル設計変更のたびにコードを修正する必要はない。これが「変更に強い」ということの正体だ。
② 型安全性の確保(防御的プログラミング)
`Select Case fldSource.Type` を挟むことで、`dbText`を数値型に流し込もうとするような無茶な処理を、事前に制御できる。`Left`関数で文字列長を制限しているのは、宛先のフィールドサイズオーバーによる実行時エラーを未然に防ぐための、現場を知る者特有の「防波堤」である。
③ 柔軟なエラーハンドリング
`Resume Next`を適切に活用することで、1フィールドのデータ不備で全処理を止めるのではなく、ログを出力して次へ進むという「バッチ処理として継続可能な設計」にしている。
—
4. エンジニアへの提言:道具に使われるな
Accessは、正しく使えば世界最強の高速開発ツールだが、適当に使えば「スパゲッティコードのゴミ溜め」になる。
DAOを扱う際、`CurrentDb`を安易に何度も呼び出してはならない。`.OpenRecordset`のコストは決して低くない。必ず変数を使い回し、`Set Nothing`でオブジェクトを解放する。そんな当たり前の所作が、長時間稼働する業務システムを安定させる唯一の道だ。
今日からあなたのコードに、この「動的判定ロジック」を組み込んでほしい。それが、あなたがただの「プログラマー」から、システムの安定を支配する「アーキテクト」へと脱皮する第一歩となるはずだ。
