ハードコーディングの呪縛を解け:ADOのFieldsコレクションで実現する「究極の汎用インポートツール」
こんにちは。今日もコードと向き合っているあなた、素晴らしいですね。
Access VBAを学び始めると、最初にぶつかる壁があります。それは「テーブルの項目が増えたり、名前が変わったりするたびに、プログラムを書き直さなければならない」という、いわゆる「ハードコーディング」の苦しみです。
`rs!UserName = “田中”` と書けば、その瞬間は動きます。しかし、もし業務仕様が変わって `UserName` が `CustomerName` に変わったら? あなたはプログラムのあちこちを探して、修正して、テストして……。これでは、自動化の恩恵が半減してしまいます。
今日は、そんな「書き換え地獄」からあなたを解放する、伝説的なエンジニアが必ず通る道「Fieldsコレクションを用いた動的なデータ操作」についてお話ししましょう。ここをクリアすれば、あなたのVBAは「単なるマクロ」から「プロフェッショナルなツール」へと進化します。
1. なぜ「名前」で指定してはいけないのか?
まずは、多くの初学者が陥る「惜しいコード」を見てみましょう。
' ❌ 避けるべき例:ハードコーディング
Sub BadImport()
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("T_顧客マスタ")
rs.AddNew
rs!氏名 = "山田太郎" ' ← ここが「名前」に依存している
rs!電話番号 = "090-0000-0000" ' ← ここも!
rs.Update
rs.Close
End Sub
このコードの弱点は、「プログラムがテーブルの構造(スキーマ)を知りすぎてしまっている」ことです。プログラムがテーブルの構造に依存すると、柔軟性が失われます。
プロのエンジニアは、こう考えます。
「プログラムは、データの『形』を知る必要はない。データ自身に『君の形(フィールド名)を教えてくれ』と聞けばいいのだ」と。
2. 救世主:ADOの `Fields` コレクション
ここで登場するのが、ADO(ActiveX Data Objects)の `Fields` コレクションです。
DAOも優秀ですが、外部データベース(SQL ServerやExcelなど)との親和性が高く、動的な操作に長けているのがADOです。ADOのRecordsetには `Fields` というコレクションがあり、ここにはテーブルの各列(フィールド)が格納されています。
重要なのは、以下の2つのアクセス方法です。
1. `rs.Fields(“氏名”).Value` :名前でアクセスする
2. `rs.Fields(0).Value` :インデックス(番号)でアクセスする
この「番号でアクセスできる」という性質こそが、汎用ツールを作るための鍵となります。
3. 実践:どんなテーブルにも対応する「汎用インポート・エンジン」
それでは、いよいよ本題です。
「元となるデータ(Recordset)」を受け取り、その中身を「宛先のテーブル」へ、フィールド名が一致するものを自動的に流し込む汎用プロシージャを構築しましょう。
このコードは、たとえテーブルの項目が10個あろうが100個あろうが、名前さえ合っていれば自動的に判別してインポートします。
Option Explicit
''' <summary>
''' 汎用データインポートプロシージャ
''' </summary>
''' <param name="srcRS">コピー元となるRecordset (ADO)</param>
''' <param name="targetTableName">書き込み先のテーブル名</param>
Public Sub GenericDataImporter(ByRef srcRS As ADODB.Recordset, ByVal targetTableName As String)
Dim targetDB As DAO.Database
Dim targetRS As DAO.Recordset
Dim i As Integer
Dim targetField As DAO.Field
Dim srcFieldName As String
Set targetDB = CurrentDb
' 1. トランザクションの開始(ここがプロの仕事!)
' 万が一エラーが起きても、中途半端なデータが残らないようにします。
On Error GoTo ErrorHandler
DBEngine.BeginTrans
' 2. 書き込み先テーブルを開く
Set targetRS = targetDB.OpenRecordset(targetTableName, dbOpenDynaset)
' 3. コピー元レコードセットの全レコードをループ
Do Until srcRS.EOF
targetRS.AddNew
' --- ここが魔法のロジック ---
' コピー元のフィールドを一つずつ順番に見ていく(インデックスで回す)
For i = 0 To srcRS.Fields.Count - 1
srcFieldName = srcRS.Fields(i).Name ' フィールド名を取得
' 書き込み先テーブルに、同じ名前のフィールドがあるか探す
On Error Resume Next
Set targetField = Nothing
Set targetField = targetRS.Fields(srcFieldName)
On Error GoTo ErrorHandler
' もし同じ名前のフィールドが見つかったら、値を代入
If Not targetField Is Nothing Then
' 値がNullの場合の処理も考慮
targetField.Value = srcRS.Fields(i).Value
End If
Next i
' --------------------------
targetRS.Update
srcRS.MoveNext
Loop
' 4. すべて成功したら確定(コミット)
DBEngine.CommitTrans
Debug.Print "インポートが正常に完了しました。"
CleanUp:
If Not targetRS Is Nothing Then targetRS.Close
Set targetRS = Nothing
Set targetDB = Nothing
Exit Sub
ErrorHandler:
' 5. エラーが発生したらすべて取り消す(ロールバック)
DBEngine.RollbackTrans
MsgBox "エラーが発生したため、インポートを中断しロールバックしました。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical
Resume CleanUp
End Sub
【解説】何が起きているのか?
1. `srcRS.Fields.Count` でループ:
コピー元のデータの数ではなく、「列の数」でループを回しています。これにより、列がいくつあっても対応可能です。
2. `srcRS.Fields(i).Name`:
「何番目の列か」から「その列の名前は何か」を引き出しています。
3. `On Error Resume Next` の戦略的利用:
「書き込み先に同じ名前の列があるか?」を試しています。もし存在しなければエラーが出ますが、それを無視して次に進むことで、「名前が一致するものだけをコピーする」というフィルタリングを実現しています。
4. トランザクション処理 (`BeginTrans` / `CommitTrans`):
これが最も重要です。100件中50件目でエラーが起きたとき、トランザクションを使っていなければ、データベースには「中途半端な50件」が残ってしまいます。これではデータ整合性が壊れます。プロは必ず「全部成功するか、全く行わないか」のどちらかを選ばせます。
4. 陥りやすい罠と、エンジニアの知恵
この素晴らしいツールを使うにあたって、いくつか注意すべき点があります。
① データ型の不一致 (Type Mismatch)
名前が一致していても、コピー元が「文字列」で、コピー先が「数値型」だとエラーになります。
対策: 実務では `IsNumeric()` や `IsDate()` を使った型チェックを入れるか、あるいは「エラーが起きたらそのレコードをスキップしてログに記録する」という設計に昇華させましょう。
② パフォーマンスの重み
大量のデータ(数万件〜)を扱う場合、1件ずつ `AddNew` すると時間がかかります。
対策: その場合は、ADOの `BatchUpdate` を検討するか、あるいはAccessの `DoCmd.TransferSpreadsheet` のような高速なネイティブ機能を利用する設計に切り替えます。
最後に:一歩先へ進むあなたへ
いかがでしたか?
「名前を直接書かない」というだけで、あなたの作ったプログラムは、まるで生き物のように柔軟に、どんなテーブル構造にも適応できるようになります。
最初は「なんだか難しそうだな」と感じるかもしれません。でも、大丈夫です。
「オブジェクトの構造を動的に探索する」という感覚を掴めたとき、あなたはもう、単なるマクロ作成者ではなく、システムを設計する「エンジニア」の入り口に立っています。
ここをクリアすれば、Access VBAの基本はバッチリですよ。
さあ、次はもっと大きな仕組みを作りにいきましょう!応援しています。
