Access VBAを掌握する極限の知見:DoCmd.TransferSpreadsheetのエラー検知と自動例外処理の極意
レガシーシステムの深部において、外部データ連携の安定性はシステムの寿命を決定づける。特に、Excelスプレッドシートからのデータインポート処理は、現場のユーザーが意図せず行うフォーマット変更や型違いの混入により、常に爆弾を抱えている。
`DoCmd.TransferSpreadsheet`は強力だが、サイレントエラーや予期せぬ中断を引き起こす代表格だ。とりわけ、インポート時に不整合が発生した際、Accessが自動生成する「インポートエラー(Import Errors)」テーブルの存在を看過しているシステムは多い。
今回は、この幽霊のように生成されるエラーテーブルをVBAで完全に制御し、システム全体の堅牢性を極限まで高める自動例外処理アーキテクチャを解説する。
—
1. インポートエラーテーブルのメカニズムとアーキテクチャの課題
`DoCmd.TransferSpreadsheet acImport`を実行した際、Accessの内部エンジン(ACE/JET)は、型ミスマッチや主キーの重複といった行単位の矛盾に直面すると、処理全体をロールバックするのではなく、「正常に取り込める行だけを取り込み、不正な行をドロップした上で、エラーの発生理由を記録した専用のテーブル」を動的に生成する。
このテーブル名は通常、対象のテーブル名(または指定したインポート名)の末尾に `$_ImportErrors` といったサフィックスが付与される。
シニアエンジニアが直面する3つの罠
1. 名前の予測不可能性: 複数セッションや並行処理において、エラーテーブル名が一意に定まらない、あるいは既存のエラーテーブルと競合する。
2. ストレージの肥大化: エラーテーブルは明示的に削除しない限りデータベース内に永続化され、Bloat(データベースの断片化)を引き起こす。
3. ユーザー体験の欠如: 黒いコンソール(あるいはVBAのエラーダイアログ)の代わりに、どの行のどのフィールドが原因で弾かれたのかをユーザーにフィードバックできない。
これを解決するためには、「実行前の状態スナップショット」「エラーテーブルの動的検出」「構造化された例外抽出」「確実なリソース解放」の4ステップをトランザクション的に実装する必要がある。
—
2. 実装コード:堅牢なインポート例外処理エンジン
以下に、実業務の現場でそのまま稼働できる、極限まで最適化されたVBAコードを示す。`CurrentDb`の適切なキャッシュ戦略と、ADOを用いたエラーテーブルの高速解析を組み込んでいる。
Option Compare Database
Option Explicit
‘ ==============================================================================
‘ 外部Excelファイルを安全にインポートし、エラーテーブルを自動監視・解析するプロシージャ
‘ ==============================================================================
Public Sub ExecuteResilientImport(ByVal filePath As String, ByVal targetTableName As String)
Dim db As DAO.Database
Dim td As DAO.TableDef
Dim errorTableName As String
Dim hasError As Boolean
Dim rsError As ADODB.Recordset
Const ERR_SUFFIX As String = “$_ImportErrors”
On Error GoTo ErrorHandler
‘ 1. セッションの確立と最適化
Set db = CurrentDb()
db.Modeless = True ‘ UIの無駄な描画を抑制しスループットを向上
‘ 2. 事前クリーンアップ:過去に取り残された同名のエラーテーブルを物理削除
errorTableName = targetTableName & ERR_SUFFIX
Call DropTableIfExists(db, errorTableName)
‘ 3. トランザクション的インポートの実行
‘ ※ TransferSpreadsheet自体は独自の内部トランザクションを持つため、
‘ エラー発生時はエラーテーブルが生成される。
DoCmd.TransferSpreadsheet _
TransferType:=acImport, _
SpreadsheetType:=acSpreadsheetTypeExcel12Xml, _
TableName:=targetTableName, _
FileName:=filePath, _
HasFieldNames:=True
‘ 4. エラーテーブルの生成有無を走査
hasError = CheckTableExists(db, errorTableName)
If hasError Then
‘ 5. エラー検知時の高度な例外処理
MsgBox “インポート処理の一部でデータ不整合が発生しました。” & vbCrLf & _
“システムが詳細な解析レポートを生成します。”, vbExclamation, “インポート警告”
‘ ADOを使用してパフォーマンスを最大化したエラー解析
Set rsError = New ADODB.Recordset
rsError.Open “SELECT FROM [” & errorTableName & “]”, CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
Call AnalyzeAndLogImportErrors(rsError)
‘ リソースの即時解放
rsError.Close
Set rsError = Nothing
‘ 必要に応じてエラーテーブルを保持(監査証跡用)または削除
‘ 今回はクリーンアップを推奨
‘ db.Execute “DROP TABLE [” & errorTableName & “]”, dbFailOnError
Else
MsgBox “外部データのインポートが正常に完了しました。”, vbInformation, “処理成功”
End If
CleanUp:
‘ 6. オブジェクトの明示的解放(メモリリークの完全阻止)
Set db = Nothing
Exit Sub
ErrorHandler:
‘ 致命的なシステムエラー(ファイルが存在しない、排他制御違反など)
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
‘ ==============================================================================
‘ 補助関数: テーブルの存在確認(DAO走査)
‘ ==============================================================================
Private Function CheckTableExists(db As DAO.Database, ByVal tableName As String) As Boolean
Dim tdf As DAO.TableDef
CheckTableExists = False
For Each tdf In db.TableDefs
If tdf.Name = tableName Then
CheckTableExists = True
Exit For
End If
Next tdf
End Function
‘ ==============================================================================
‘ 補助関数: 既存テーブルの安全な削除
‘ ==============================================================================
Private Sub DropTableIfExists(db As DAO.Database, ByVal tableName As String)
On Error Resume Next
db.TableDefs.Delete tableName
On Error GoTo 0
End Sub
‘ ==============================================================================
‘ エラー解析エンジン:ユーザーへの実用的なフィードバック生成
‘ ==============================================================================
Private Sub AnalyzeAndLogImportErrors(rs As ADODB.Recordset)
Dim fld As ADODB.Field
Int errCount As Long
Dim logMessage As String
errCount = 0
logMessage = “— インポートエラー詳細ログ —” & vbCrLf
Do While Not rs.EOF And errCount < 10 ' 上位10件までを抽出 errCount = errCount + 1 logMessage = logMessage & "行番号 (ErrorLine): " & rs.Fields("ErrorLine").Value & vbCrLf logMessage = logMessage & "フィールド (Field): " & rs.Fields("FieldName").Value & vbCrLf logMessage = logMessage & "理由 (ErrorDescription): " & rs.Fields("ErrorDescription").Value & vbCrLf logMessage = logMessage & "----------------------------------" & vbCrLf rs.MoveNext Loop If Not rs.EOF Then logMessage = logMessage & "... 他多数のエラーがあります。エラーテーブルを確認してください。" End If ' 実運用ではここでテキストファイル出力や専用ログテーブルへの書き込みを行う Debug.Print logMessage MsgBox logMessage, vbExclamation, "エラー詳細プレビュー" End Sub ---
3. チーフアーキテクトが教える「現場の知見」とパフォーマンス最適化
上記のコードベースをプロダクション環境に投入するにあたり、知見を共有する。
1. `CurrentDb` のキャッシュ特性とメモリ管理
`CurrentDb`メソッドは呼び出すたびに新しい`DAO.Database`オブジェクトのインスタンスをヒープ上に生成する。これをループ内や無秩序に多用すると、Accessの内部ポインタが枯渇し、いわゆる「メモリリーク(リソース不足)」を引き起こす。
上記のコードでは、プロシージャの最初に一度だけ変数 `db` に格納し、処理の終了時に必ず `Set db = Nothing` で参照を断ち切っている。この規律を破るベテラン(気取りの素人)のコードが、システムを数ヶ月で不安定にする元凶となる。
2. ADOとDAOの使い分けの極意
エラーテーブルの走査にはあえて ADO(ActiveX Data Objects) の `CurrentProject.Connection` を採用している。
大規模なエラーテーブルをDAOのレコードセットで全件走査すると、Jet/ACEエンジンのオーバーヘッドによってパフォーマンスが著しく低下する。前方読込専用(`adOpenForwardOnly`)かつロックなし(`adLockReadOnly`)のADO接続を用いることで、メモリフットプリントを最小限に抑えつつ、高速なストリーミング処理が可能になる。
3. トランザクションの限界を見極める
`DoCmd.TransferSpreadsheet` 自体は、VBAの `BeginTrans` / `CommitTrans` のスコープ外で動作する特殊なコマンドである。そのため、インポート途中のロールバックをVBA側から完全に制御することはできない。だからこそ、「実行前の予測」「失敗時のエラーテーブル検知」「事後処理としてのログ化・クリーンアップ」という、外側からの包囲網による例外処理設計が必須となるのだ。
—
結言
レガシーデータベースであるAccessは、その手軽さゆえに「場当たり的なコード」が蔓延しやすい。しかし、アーキテクチャの本質を理解したエンジニアの手にかかれば、Enterprise環境に匹敵する堅牢なデータパイプラインへと昇華させることが可能だ。
エラーテーブルの自動監視はその第一歩に過ぎない。システムを信頼性の要塞へと変えるため、今すぐあなたのコードベースにある `TransferSpreadsheet` の実装を見直し、この堅牢な例外処理パターンを実装してほしい。
