Access VBAを掌握する極限の知見:CSVインポート時にテーブル定義を自動拡張する柔軟なデータ取込機能
開発現場でシステムを運用していると、必ずと言っていいほど直面する問題がある。
「他システムから連携されるCSVの仕様が、事前通告なしに変更された」
「新しい項目(列)が追加されたせいで、インポート処理がエラーで止まった」
この手のトラブルは、業務部門からの「データを取り込めないんだけど!」という怒りの連絡と共にやってくる。
その都度、デザインビューを開いてフィールドを手動追加し、インポートクエリやVBAのSQLを書き換える?
冗談じゃない。そんな非効率な運用を続けているうちは、プロの自動化エンジニアとは言えない。
今回は、CSVのヘッダー行を動的に解析し、存在しないフィールドがあれば自動的にテーブル定義を拡張(ALTER TABLE / TableDef操作)した上で、データを完遂させる「自己適応型」のインポートエンジンの設計思想と実装を伝授する。
—
なぜ「固定化されたスキーマ」のインポートは破綻するのか
多くのプログラマブルなインポート処理は、次のようなアプローチをとる。
1. あらかじめ決まったフィールドを持つテーブルにインポートする。
2. CSVの列数が違ったり、見慣れない項目名があったりすると、`RunCommand acCmdImportExport` や `DoCmd.TransferText` が容赦なくエラーを吐いて止まる。
この設計がクソな理由は、「外部データの変化に対する耐性(レジリエンス)がゼロ」だからだ。
システムが人間を縛るのではなく、システムが人間の変化に寄り添うべきだ。テーブル定義をデータの来航に合わせて「動的に拡張」できれば、CSVの仕様変更ごとのコード修正から完全に解放される。
—
堅牢なインポートエンジンを構築するための3つの鉄則
Access VBAで動的なスキーマ変更とデータ取込を実装するにあたり、以下の極限知見を頭に叩き込んでおいてほしい。
1. `TableDef` コレクションの鮮度管理
VBAからテーブル構造を変更する場合、`CurrentDb.TableDefs` を操作する。しかし、DAOのオブジェクトキャッシュは非常に厄介で、意図したタイミングで最新の状態を反映させないと、存在しないフィールドへのアクセスでハングアップや実行時エラーを引き起こす。操作の前後は必ず `Refresh` を挟むか、オブジェクト変数を適切に再取得すること。
2. データ型推論の罠と安全策
CSVから動的に追加されるフィールドのデータ型をどう決定するか?
厳密にやろうとすれば、CSVの全行を走査して数値か日付か判定すべきだが、数万行あるファイルでそれをやるとパフォーマンスが死ぬ。
実務的な解として、「初期追加時はすべてテキスト型(Text / 255文字、あるいはMemo型)で受け受け、後続の正規化クエリで型変換する」のが最も安全で破綻しない。テーブル定義の拡張で最も避けるべきは「型ミスマッチによるインポート全体のロールバック」である。
3. トランザクションとトランケートの分離
インポート途中で予期せぬ例外が発生した際、中途半端なデータと、勝手に追加された無駄なフィールドだけが残る最悪の事態を防ぐため、エラーハンドリングとトランザクション制御(Workspace / CommitTrans)を徹底する。
—
【プロダクションコード】自動拡張インポートモジュール
以下のコードは、指定したCSVファイルの1行目(ヘッダー)を読み込み、対象テーブルのフィールドと比較し、不足しているフィールドを動的に `Append`(追加)した上で、データを流し込む完成されたモジュールである。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 処理名 : 柔軟なCSV自動拡張インポート
‘ 概要 : CSVのヘッダーを解析し、テーブルに不足している項目を自動追加して取込
‘ =========================================================================
Public Sub ExecuteFlexibleImport(ByVal csvFullPath As String, ByVal targetTableName As String)
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Dim fso As Object
Dim ts As Object
Dim headerLine As String
() As String
Dim colName As String
Dim i As Long
Dim fieldExists As Boolean
Dim ws As DAO.Workspace
Set db = CurrentDb
Set ws = DBEngine.Workspaces(0)
‘ 1. CSVファイルの存在確認とオープン
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FileExists(csvFullPath) Then
MsgBox “指定されたCSVファイルが存在しません。” & vbCrLf & csvFullPath, vbCritical, “インポートエラー”
Exit Sub
End If
On Error GoTo ErrorHandler
‘ トランザクション開始
ws.BeginTrans
‘ 2. CSVの1行目(ヘッダー)を取得
Set ts = fso.OpenTextFile(csvFullPath, 1, False, -2) ‘ -2 = TristateUseDefault (ANSI/SJIS対応)
If ts.AtEndOfStream Then
MsgBox “CSVファイルが空です。”, vbExclamation, “インポート警告”
ts.Close
Exit Sub
End If
headerLine = ts.ReadLine
ts.Close
headers = Split(headerLine, “,”)
‘ 3. テーブルの存在確認、なければ新規作成
If Not TableExists(targetTableName) Then
Set tdf = db.CreateTableDef(targetTableName)
‘ 主キーやIDが必要な場合はここで自動採番ID等を入れても良い
‘ 今回はシンプルに全列テキスト型で初期作成する例
For i = LBound(headers) To UBound(headers)
‘ ダブルクォーテーションの除去
colName = Replace(Trim(headers(i)), “”””, “”)
Set fld = tdf.CreateField(colName, dbText, 255)
tdf.Fields.Append fld
Next i
db.TableDefs.Append tdf
db.TableDefs.Refresh
End If
‘ 4. 既存テーブル定義とCSVヘッダーを突合し、不足フィールドを動的追加
Set tdf = db.TableDefs(targetTableName)
For i = LBound(headers) To UBound(headers)
colName = Replace(Trim(headers(i)), “”””, “”)
‘ フィールドが存在するかチェック
fieldExists = False
For Each fld In tdf.Fields
If StrComp(fld.Name, colName, vbTextCompare) = 0 Then
fieldExists = True
Exit For
End If
Next fld
‘ 存在しない場合は、フィールドを追加(ALTER TABLE相当のDAO操作)
If Not fieldExists Then
Set fld = tdf.CreateField(colName, dbText, 255)
tdf.Fields.Append fld
Debug.Print “【スキーマ自動拡張】テーブル [” & targetTableName & “] にフィールド [” & colName & “] を追加しました。”
End If
Next i
‘ テーブル定義の変更を確定
tdf.Refresh
db.TableDefs.Refresh
‘ 5. Access標準のTransferTextでインポート実行
‘ ※注意: あらかじめインポート定義(仕様)を作成している場合はそれを使用するが、
‘ 今回はヘッダー付きCSVを直接インポートする標準挙動を利用
DoCmd.TransferText acImportDelim, , targetTableName, csvFullPath, True
‘ トランザクションコミット
ws.CommitTrans
MsgBox “CSVのインポートおよびテーブル定義の自動拡張が正常に完了しました。”, vbInformation, “完了”
Exit Sub
ErrorHandler:
ws.Rollback
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ 補助関数: テーブル存在確認
Private Function TableExists(ByVal tableName As String) As Boolean
Dim tdf As DAO.TableDef
TableExists = False
For Each tdf In CurrentDb.TableDefs
If StrComp(tdf.Name, tableName, vbTextCompare) = 0 Then
TableExists = True
Exit Function
End If
Next tdf
End Function
—
このアーキテクチャが現場にもたらす圧倒的なメリット
1. 運用コストの劇的な削減
上流工程(他システム)の都合でCSVの項目が勝手に増えても、現場のオペレーターがパニックになることはなくなる。システムが自律的にテーブルを拡張し、データを飲み込むからだ。
2. 保守性の担保
「なぜフィールドが追加されたのか」の足跡は、デバッグイミディエイトウィンドウや、必要であればログテーブルに出力するように改修することで、監査証跡としても機能する。
3. トランザクションによる安全性
万が一、CSVのフォーマットが崩れていて途中行でエラーが起きても、`ws.Rollback` によりデータベースが汚染されるリスクを完全にシャットアウトしている。
—
チーフアーキテクトからの最後のアドバイス
動的なスキーマ変更は諸刃の剣である。今回は「テキスト型での自動拡張」を基本としたが、本格的なエンタープライズ環境に耐えるシステムにするためには、このインポート処理の後に「適切なデータ型(Long, Currency, Date/Timeなど)へのキャストとバリデーションを行うクエリ層」を必ずワンセットで用意すること。
「自動化とは、手抜きではなく、変化に強い強靭な仕組みを作ることだ。」
このコードをあなたのAccessアプリに組み込み、明日からのCSV連携トラブルを過去のものにしてほしい。
