【入門編】ADOのFieldsコレクションを動的に操作する:汎用的なデータインポートツールの作り方 – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

ハードコーディングの呪縛を解け: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の基本はバッチリですよ。
さあ、次はもっと大きな仕組みを作りにいきましょう!応援しています。

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