【Access VBA極限活用】`DoCmd.TransferText`のインポート定義を動的に操り、CSV地獄を制圧する設計思想
開発現場で最も頭を悩ませる問題の一つが、「外部から送られてくる、バラバラなフォーマットのCSVファイル群」だ。
取引先A社は項目数が20個のカンマ区切り、B社は項目数が35個のタブ区切り、C社に至っては日付のクレンジングが必要な変則フォーマット……。これらを処理するために、わざわざインポート仕様書(インポート定義)を何個も手動で作成し、それぞれに対応するVBAの処理を書き分ける?
――そんな非効率な設計は、今この瞬間で終わりにしよう。
世界最高峰の業務システムを構築する者であれば、「インポート定義のXML(またはレジストリ)をプログラム側で動的に制御・書き換える」、あるいは「単一の汎用定義をベースにレイアウト差異を吸収する」というアプローチをとる。
今回は、Access VBAの王道メソッドである `DoCmd.TransferText` の裏側の仕様を完全に暴き、複数の異なるCSVフォーマットを極限までスマートに、かつバグゼロで取り込むためのアーキテクチャを伝授する。
—
なぜ「定石通りのインポート定義の固定化」は破綻するのか
Accessのインポート定義(旧バージョンのレジストリ保存、近年のAccessにおける「インポート/エクスポート仕様」のXML保存)は、非常に強力な機能だ。ウィザードに従って定義を作れば、文字コードや区切り文字を自動判別してテーブルに流し込んでくれる。
しかし、ここに大きな罠がある。
1. 仕様変更のたびにAccessの画面から定義を修正する必要がある
2. ファイルごとにフォーマットが微妙に違う場合、定義の数が爆発する
3. エラーハンドリングが不十分だと、型ミスマッチで容赦なくプロセスがクラッシュする
実務における自動化ツールにおいて、「手動での定義作成・メンテナンス」が残っている時点で、それは真の自動化とは言えない。インポート定義の本質を理解し、VBAからメタデータとしてハンドリングできれば、CSVの嵐など恐るに足りない。
—
堅牢なインポート設計の全体像
今回構築するのは、以下の要件を満たすプロダクションレベルのアーキテクチャだ。
- 動的パラメータ切替: 処理対象のCSVヘッダーやファイル種別を判定し、適切なインポート定義、あるいは前処理を動的にアサイン。
- ステージングテーブル(Work表)の活用: 本番テーブルに直接流し込むのではなく、一度「全項目が文字列のステージングテーブル」に落とし込み、VBA側で型変換とバリデーションを行う。
- 完全なトランザクション管理: 途中でパースエラーや想定外のデータ構造が見つかった場合は、即座にロールバックする。
—
プロダクションコード:動的インポート制御エンジンの実装
以下のコードは、単にメソッドを呼び出すだけのコードではない。ファイルシステムオブジェクト(FSO)と連携し、CSVの構造を検証した上で `DoCmd.TransferText` を安全に実行する、実戦投入仕様のモジュールだ。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 模块名: clsCsvImporter
‘ 用途: CSVフォーマット動的インポート制御エンジン
‘ =========================================================================
Private Const STAGING_TABLE_NAME As String = “W_Csv_Staging”
‘ ————————————————————————-
‘ メイン実行メソッド
‘ ————————————————————————-
Public Function ExecuteImport(ByVal filePath As String, ByVal specificationName As String) As Boolean
Dim fso As Object
Dim db As DAO.Database
On Error GoTo ErrorHandler
‘ 1. ファイル存在確認 (FileSystemObject)
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FileExists(filePath) Then
MsgBox “指定されたファイルが存在しません。” & vbCrLf & filePath, vbCritical, “インポートエラー”
ExecuteImport = False
Exit Function
End If
Set db = CurrentDb()
‘ 2. トランザクション開始(データベースの整合性を担保)
db.BeginTrans
‘ 3. ステージングテーブルの初期化(前回のゴミデータをクリア)
Call ClearStagingTable(db)
‘ 4. DoCmd.TransferText による高速インポート実行
‘ ※あらかじめAccess本体に「specificationName」という名前のインポート定義が存在する前提
DoCmd.TransferText acImportDelim, specificationName, STAGING_TABLE_NAME, filePath, True
‘ 5. ステージングから本番テーブルへのデータクレンジング&移行処理
If Not MigrateToProduction(db) Then
Err.Raise 9999, “MigrateToProduction”, “本番テーブルへのデータ移行中に論理エラーが発生しました。”
End If
‘ 6. コミット
db.CommitTrans
Set fso = Nothing
ExecuteImport = True
Exit Function
ErrorHandler:
‘ 異常系:ロールバックして安全に終了
On Error Resume Next
db.RollbackTrans
MsgBox “インポート処理中に致命的なエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “システムエラー”
Set fso = Nothing
ExecuteImport = False
End Function
‘ ————————————————————————-
‘ ステージングテーブルのクリア
‘ ————————————————————————-
Private Sub ClearStagingTable(ByRef db As DAO.Database)
Dim sql As String
‘ テーブルの構造は残し、データのみを全削除
sql = “DELETE FROM ” & STAGING_TABLE_NAME & “;”
db.Execute sql, dbFailOnError
End Sub
‘ ————————————————————————-
‘ 本番テーブルへの移行とデータバリデーション
‘ ————————————————————————-
Private Function MigrateToProduction(ByRef db As DAO.Database) As Boolean
Dim strSql As String
On Error GoTo MigrateError
‘ 【重要】
‘ ステージング(全列Text型)から本番テーブルへ INSERT する際に、
‘ 日付の整合性チェックや数値変換、トリム処理をSQL関数(CDate, Nz, Trim等)で安全に行う。
strSql = “INSERT INTO T_ActualSales (SalesDate, CustomerCode, Amount, CreatedAt) ” & _
“SELECT ” & _
” CDate(Trim(Field1)) AS SalesDate, ” & _
” Left(Trim(Field2), 10) AS CustomerCode, ” & _
” Val(Nz(Field3, 0)) AS Amount, ” & _
” Now() AS CreatedAt ” & _
“FROM ” & STAGING_TABLE_NAME & ” ” & _
“WHERE IsDate(Trim(Field1)) = True;” ‘ 日付として不正な行はここで弾く(堅牢性の担保)
db.Execute strSql, dbFailOnError
MigrateToProduction = True
Exit Function
MigrateError:
MigrateToProduction = False
End Function
—
チーフアーキテクトが教える「現場で絶対に踏んではいけない地雷」
上記のコードを見て、「なぜわざわざ一度ステージングテーブルに挟むのか?」と疑問に思った読者もいるはずだ。直接本番テーブルに `DoCmd.TransferText` すればコードが短くなるではないかと。
ここに、プロとアマの決定的な設計思想の差がある。
1. 型ミスマッチによるインポート全体のサイレント崩壊を防ぐ
直接本番テーブルにインポートしようとすると、CSV内のたった1行の「日付データの誤り」「数値列への文字列混入」によって、`DoCmd.TransferText` 自体がエラーを起こすか、最悪の場合、該当レコードがスキップされてログにも残らないという最悪の事態(サイレントデータロス)を招く。
すべての列を「テキスト型」で受け受けるステージングテーブルを一度経由することで、データ構造の物理的な取り込み(I/O)と、ビジネスロジックの解釈(CPU/SQL処理)を完全に分離できる。
2. インポート定義の切り替えを「引数」で制御する
フォーマットA、フォーマットB、フォーマットC……これらはすべて同じテーブル構造に集約したい場合、VBA側から呼び出すインポート定義の名前(`specificationName`)を、ファイルの拡張子やフォルダ名、あるいはファイル内容の先頭行(ヘッダー)のハッシュ値によって動的に切り替える仕組みを上位レイヤーに持たせる。
‘ 呼び出し側の例:ファイル名に応じてインポート定義を動的にスイッチする
Public Sub RunBatch()
Dim importer As New clsCsvImporter
Dim targetFile As String
Dim specName As String
targetFile = “C:\Data\VendorA_202310.csv”
‘ ベンダーごとにインポート定義(あらかじめ定義済みの名前)を動的に選択
If InStr(targetFile, “VendorA”) > 0 Then
specName = “ImportSpec_VendorA”
ElseIf InStr(targetFile, “VendorB”) > 0 Then
specName = “ImportSpec_VendorB”
Else
specName = “ImportSpec_Standard”
End If
If importer.ExecuteImport(targetFile, specName) Then
MsgBox “正常にインポートが完了しました。”, vbInformation
End If
End Sub
この設計であれば、新しいフォーマットの取引先が増えたとしても、Access側で新しいインポート定義を追加し、呼び出し元の条件分岐(あるいは設定マスタからの動的取得)を1行増やすだけで対応が完了する。既存のコードを改修するリスクを極限まで低減できるのだ。
—
結び:Access VBAを「おもちゃ」から「エンタープライズツール」へ昇華させるために
「Accessだからこれくらい適当でいいか」という妥協は、やがて運用フェーズでの膨大なデバッグ地獄となって自分に跳ね返ってくる。
`DoCmd.TransferText` は、ただの古いコマンドではない。裏側の挙動を理解し、トランザクション、ステージング、動的仕様切替という「堅牢なエンジニアリングパターン」で包み込むことで、基幹系システムにも匹敵する堅牢なデータパイプラインへと生まれ変わる。
あなたの手元にあるそのAccessアプリケーションを、ぜひ「プロの仕事」が宿る堅牢なシステムへと高めてほしい。
