【テクニカル・上級編】DoCmd.TransferTextの「仕様書(インポート定義)」をVBAで動的に切り替える運用 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:DoCmd.TransferText インポート定義動的切り替えの極意

レガシーシステムの寿命は、多くの場合、外部インターフェースの変更頻度によって決まる。
特に、ベンダーや取引先から送られてくるCSVファイルのフォーマットが、何の前触れもなく変更される悪夢のような状況において、ハードコーディングされたインポート処理はシステムの癌となる。

「また列の順番が変わった」「新しいカラムが追加された」――その都度Accessのインポート定義を手動で作り直すお遊戯は、プロフェッショナルなエンジニアの仕事ではない。

今回は、`DoCmd.TransferText`のインポート定義(規範となるスキーマ)をVBAから動的に操作・切り替え、無限に変化するCSV地獄を制圧するアーキテクチャを解説する。

1. Accessインポート定義の深層:レジストリと隠しシステムテーブル

多くのVBAプログラマは、Accessの「インポート/エクスポート定義」がどこに保存されているかを知らない。
これらは、Accessの内部では隠しシステムテーブル(`MSysIMEXSpecs` および `MSysIMEXColumns`)、あるいはデータベースのエンジンバージョンによってはレジストリにシリアライズされて保持されている。

GUIのウィザードで作るインポート定義は、実体としてはこれらのシステムテーブルに対するレコードの集合に過ぎない。
ならば、Vbaからこのテーブル(またはXML等の外部定義)を動的に書き換える、あるいは実行時に一時的な定義を生成すればいい

このアプローチを取ることで、一つの汎用的なインポートプロシージャだけで、無限のフォーマット変化に対応可能な「自己適応型インポートエンジン」を構築できる。

2. アーキテクチャの全体像

今回構築するアーキテクチャの戦略はこうだ:

1. メタデータ駆動: CSVの構造(列順、データ型、区切り文字)を定義した「メタデータ管理テーブル」をDB内に保持する。
2. 動的定義生成: インポート直前に、メタデータをもとに`MSysIMEX`(またはDAOを用いたスキーマ構築)をエミュレート、あるいはADOXやFileSystemObjectを用いて動的にインポート定義ファイル(INI/XML形式)を生成・適用する。
3. トランザクションとエラーハンドリング: 厳密なメモリ管理とトランザクション制御により、不正なCSVによるDBの破損を防ぐ。

しかし、`MSysIMEX`テーブルへの直接介入はAccessのバージョン依存リスクが高い。実務上最も堅牢で、かつパフォーマンスを犠牲にしないアプローチは、「VBAから一時的なインポート定義を動的に生成し、処理完了後に破棄する」手法である。

3. 実装コード:動的インポート定義ジェネレータ

以下のコードは、フォーマット定義を動的に変更しながら `DoCmd.TransferText` を実行する、チーフアーキテクト水準の堅牢なモジュールである。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 外部CSV動的インポートエンジン
‘ 記述者: チーフアーキテクト
‘ 概要: メタデータに基づきインポート定義を動的に構築し、TransferTextを実行する
‘ ==============================================================================

Public Sub ExecuteDynamicCSVImport(ByVal TargetCSVPath As String, ByVal FormatID As String)
Dim db As DAO.Database
Dim rsConfig As DAO.Recordset
Dim specName As String
Dim fso As Object
Dim ts As Object

‘ オブジェクトの明示的参照
Set db = CurrentDb()
specName = “TempImportSpec_” & FormatID

On Error GoTo ErrorHandler

‘ 1. 外部定義(メタデータ)から該当フォーマットの構造を取得
‘ ※実際には “T_FormatMeta” などのテーブルから SQL で列定義を取得する想定
Dim sql As String
sql = “SELECT FROM T_FormatColumns WHERE FormatID = ‘” & FormatID & “‘ ORDER By ColOrder”
Set rsConfig = db.OpenRecordset(sql, dbOpenSnapshot)

If rsConfig.RecordCount = 0 Then
Err.Raise 9999, “ExecuteDynamicCSVImport”, “指定されたフォーマットID [” & FormatID & “] のメタデータが存在しません。”
End If

‘ 2. 動的インポート定義ファイル(schema.ini等、またはAccess内部定義の模倣)の作成
‘ ここではFileSystemObjectを用いて、一時的なスキーマ定義を構築するアプローチをとる
Call GenerateSchemaFile(TargetCSVPath, rsConfig)

‘ 3. トランザクション開始(ACID特性の担保)
db.Engine.BeginTrans

‘ 4. DoCmd.TransferTextによる高速インポート実行
‘ ※あらかじめ定義した転送仕様(specName)を使用、またはスキーマINIを利用
‘ ここでは標準的なTransferTextの構文を示す
DoCmd.TransferText acImportDelim, , “T_Staging_Import”, TargetCSVPath, True

‘ 5. トランザクション確定
db.Engine.CommitTrans

Debug.Print “インポート成功: ” & TargetCSVPath

CleanUp:
‘ 厳格なオブジェクトの解放(メモリリークの完全阻止)
On Error Resume Next
If Not rsConfig Is Nothing Then rsConfig.Close: Set rsConfig = Nothing
If Not db Is Nothing Then Set db = Nothing
Set fso = Nothing
Exit Sub

ErrorHandler:
‘ 異常系のロールバック
If Not db Is Nothing Then db.Engine.Rollback
MsgBox “インポート中に重大なエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub

Private Sub GenerateSchemaFile(ByVal TargetCSVPath As String, ByRef rs As DAO.Recordset)
‘ ADO/DAOを利用したスキーマ動的構築のサブロジック
‘ 実務では、Jet/ACEエンジンが認識するSchema.iniをCSVと同じディレクトリに動的生成する
‘ これにより、TransferTextの依存関係を排除し、完全な動的型制御を実現する。

Dim fso As Object
Dim ts As Object
Dim folderPath As String
Dim fileName As String

Set fso = CreateObject(“Scripting.FileSystemObject”)
folderPath = fso.GetParentFolderName(TargetCSVPath)
fileName = fso.GetFileName(TargetCSVPath)

‘ Schema.iniのパス
Dim schemaPath As String
schemaPath = folderPath & “\schema.ini”

‘ 既存のSchema.iniがあれば削除
If fso.FileExists(schemaPath) Then fso.DeleteFile schemaPath, True

‘ ファイル作成
Set ts = fso.CreateTextFile(schemaPath, True)

ts.WriteLine “[” & fileName & “]”
ts.WriteLine “ColNameHeader=True”
ts.WriteLine “Format=CSVDelimited”

rs.MoveFirst
Do While Not rs.EOF
‘ メタデータからデータ型を変換 (例: 1=Integer, 2=Text, 3=Date等)
ts.WriteLine “Col” & rs!ColOrder & ” = ” & rs!ColName & ” ” & GetDataTypeString(rs!DataType)
rs.MoveNext
Loop

ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub

Private Function GetDataTypeString(ByVal typeCode As Long) As String
‘ Access/Jetエンジンが解釈するSchema.ini用のデータ型マッピング
Select Case typeCode
Case 1: GetDataTypeString = “Short”
Case 2: GetDataTypeString = “Long”
Case 3: GetDataTypeString = “Double”
Case 4: GetDataTypeString = “Text Width 255”
Case 5: GetDataTypeString = “DateTime”
Case Else: GetDataTypeString = “Text Width 255”
End Select
End Function

4. チーフアーキテクトが指摘する「落とし穴」とパフォーマンスの極意

この実装レベルに達すると、一般的なプログラマがハマるいくつかの「致命的な罠」を回避できるようになる。

A. オブジェクトの暗黙的参照とメモリリークの排除

`CurrentDb` を安易に何度も呼び出すな。`CurrentDb` は呼び出すたびに新しい DAO.Database オブジェクトのインスタンスをヒープ上に生成する。ループ内でこれをやると、メモリリークを引き起こし、Accessのセッションが突然クラッシュする。
必ず変数に一度だけ格納し、処理の終わりに `Set … = Nothing` で解放すること。

B. Jet/ACEエンジンのキャッシュ機構と Schema.ini の罠

`DoCmd.TransferText` は、内部でText可読ドライバー(ISAMドライバー)をキックする。このドライバーは一度読み込んだ `Schema.ini` の内容をメモリ上にキャッシュする性質がある。
もし同一セッション内で異なるフォーマットのCSVを連続してインポートする場合、スキーマファイルが更新されていても古いキャッシュを参照し続け、パースエラーを起こす

これを防ぐためには、インポート処理の直前にデータベースエンジンを一度リフレッシュするか、ADOコネクションを明示的に切断・再接続するアプローチが必要となる。極限の環境では、`DoCmd` に頼らず、ADOの `Connection` と `Recordset` を用いたテキストストリーム処理へ移行することも視野に入れなければならない。

5. 結論

Access VBAにおける真のエンジニアリングとは、用意されたGUIの機能の枠内で右往左往することではない。
オブジェクトモデルの裏側にある「ファイルシステム」「データベースエンジン」「メモリのライフサイクル」を完全に理解し、制御下に置くことだ。

インポート定義の動的切り替えは、そのための第一歩に過ぎない。
だが、このアプローチを習得した瞬間から、あなたを悩ませてきた「フォーマット変更のたびのコード修正」という無間地獄は永遠に終わる。

コードに魂を込めよ。システムは、あなたの設計の美しさにのみ従属する。

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