【Access VBA極限知見】存在しないテーブルへの恐怖を断つ:`AllTables`による防御的プログラミングの極意
レガシーシステムの深部、あるいは複雑怪奇に拡張された社内データベースの保守において、最も忌むべきものは「実行時エラーによる予期せぬプロセスの停止」である。
特に、外部システムからのCSVインポート、動的に生成される一時ワークテーブル、あるいはユーザー権限や環境依存によって存在したりしなかったりするテーブル群を扱う際、愚直に `CurrentDb.OpenRecordset` や `DoCmd.OpenTable` を叩くコードは、時限爆弾を抱えているのと同義だ。エラーハンドラー(`On Error Resume Next`)に頼り切ったコードは、本来検知すべきクリティカルな障害まで闇に葬り、メモリリークやステートの汚染を引き起こす。
我々はプロのアーキテクトとして、エラーが起きてから対処するのではなく、「起きる前に知る(あるいは存在しないことを見越して制御する)」防御的プログラミングを徹底しなければならない。
今回は、Accessオブジェクトモデルの深層にアプローチし、`Application.CurrentData.AllTables` を用いて、ノーコストかつエレガントにテーブルの存在を判定・回避する極限の知見を授けよう。
—
1. なぜ `On Error Resume Next` は悪手なのか?
多くの初学者、あるいは中途半端なスキルを持つプログラマは、テーブルの存在確認を以下のように書きがちだ。
‘ 【アンチパターン】絶対に真似してはならないレガシーコード
Public Sub BadExample_CheckTable()
Dim db As DAO.Database
Dim rs As DAO.Recordset
On Error Resume Next ‘ エラーを無視する最悪の構文
Set db = CurrentDb
Set rs = db.OpenRecordset(“T_Dynamic_Import”, dbOpenTable)
If Err.Number <> 0 Then
‘ エラー番号で判定しようとする愚行
MsgBox “テーブルが存在しません”, vbCritical
Exit Sub
End If
On Error GoTo 0 ‘ エラーMangleの解除忘れはバグの温床
‘ 以降の処理…
End Sub
このコードの何が問題か。
1. パフォーマンスの著しい劣化: 存在しないオブジェクトへのアクセスは、COMレイヤーを巻き込んだ重厚な例外処理フローを発生させ、無駄なCPUサイクルを消費する。
2. 予期せぬエラーの隠蔽: もし「テーブルが存在しない」のではなく、「テーブルは存在するが排他ロックされている(エラー 3265 や 3046 等)」場合でも、すべて「存在しない」と誤認してしまう。
3. VBAランタイムのステート汚染: `Err` オブジェクトのクリア漏れや、エラーハンドリングコンテキストの汚染は、後続の予測不可能な挙動(ゴーストバグ)の引き金となる。
真のエンジニアは、例外を「制御フロー」として使わない。状態を事前に検証(Inspection)してから実行するのだ。
—
2. `Application.CurrentData.AllTables` の正体とライフサイクル
Accessのオブジェクトモデルにおいて、`CurrentData` コレクションは、データベースの定義情報(メタデータ)を司る。その中にある `AllTables` は、カレントデータベースに存在するすべてのテーブル(リンクテーブルを含む)の `AccessObject` を格納したコレクションだ。
特筆すべきは、このコレクションへのアクセスが、DAOの `Database.TableDefs` よりも圧倒的に軽量であるという点だ。
`TableDefs` は、システムテーブル(MSysObjects等)への低レベルなバインディングや、各テーブルの詳細な属性・セキュリティ情報を伴ってインスタンス化されるため、存在チェックのループを回すと微小ながら確実にパフォーマンスを蝕む。一方、`AllTables` はメタデータの軽量なラッパーであり、メモリ消費量も最小限に抑えられている。
`AllTables` を利用した安全な存在確認関数
以下のコードは、指定したテーブル名が `AllTables` コレクションに存在するかを、大文字小文字を区別せずに高速に判定するファンクションである。
‘ =================================================================================
‘ módulo: ModDatabaseUtility
‘ 概要: テーブルの存在をApplication.CurrentData.AllTablesで安全に検証する
‘ =================================================================================
Public Function ExistsTable(ByVal TableName As String) As Boolean
Dim obj As AccessObject
Dim found As Boolean
found = False
‘ AllTables コレクションをイテレート
‘ ※注意: For Each は内部でクエリのオーバーヘッドが少ない最適化された列挙を行う
For Each obj In Application.CurrentData.AllTables
‘ VBAのStrCompを使用し、大文字小文字を区別せず(vbTextCompare)比較する
If StrComp(obj.Name, TableName, vbTextCompare) = 0 Then
found = True
Exit For
End If
Next obj
ExistsTable = found
‘ オブジェクト変数の明示的解放(VBAにおけるベストプラクティス)
Set obj = Nothing
End Function
—
3. 実践:動的テーブル連携プロセスの構築
では、この `ExistsTable` 関数を実業務のシステム間連携プロセスに組み込んでみよう。
ここでは、「前日分のデータが格納された一時テーブルが存在すればそれをインポートし、なければ新規作成してデフォルト値を流し込む」という、堅牢なバッチ処理の骨組みを示す。
‘ =================================================================================
‘ プロシージャ名: ProcessDailyImport
‘ 概要: 存在しないテーブルへのアクセスを完全に回避し、動的にフローを分岐させる
‘ =================================================================================
Public Sub ProcessDailyImport()
Const TARGET_TABLE As String = “T_Import_202X”
Dim db As DAO.Database
Dim td As DAO.TableDef
Set db = CurrentDb()
On Error GoTo ErrorHandler ‘ 予期せぬIOエラー等のみを捕捉する本来のエラーハンドラ
If ExistsTable(TARGET_TABLE) Then
‘ — ケースA: テーブルが存在する場合の処理 —
Debug.Print “対象テーブル [” & TARGET_TABLE & “] を検知。インポート処理を開始します。”
‘ ここに安全なレコードセット処理を記述
‘ 例: DoCmd.TransferText … など
Else
‘ — ケースB: テーブルが存在しない場合のフォールバック処理 —
Debug.Print “対象テーブル [” & TARGET_TABLE & “] が存在しません。新規作成します。”
‘ 動的にテーブル定義を行う(DAOの活用)
Set td = db.CreateTableDef(TARGET_TABLE)
‘ フィールドの定義
With td
.Fields.Append .CreateField(“ID”, dbLong)
.Fields.Append .CreateField(“DataValue”, dbText, 255)
.Fields.Append .CreateField(“CreatedAt”, dbDate)
End With
‘ データベースへの登録
db.TableDefs.Append td
Debug.Print “テーブルの動的生成が完了しました。”
End If
CleanUp:
‘ 厳格なリソース解放
If Not td Is Nothing Then Set td = Nothing
If Not db Is Nothing Then Set db = Nothing
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub
—
4. チーフアーキテクトからの深淵なる提言:パフォーマンスとメモリ管理の極意
このアーキテクチャを導入するにあたり、シニアエンジニアとして知っておくべき「裏の仕様」をいくつか伝授する。
1. `CurrentDb` の乱用に警戒せよ
`CurrentDb` 関数は、呼び出されるたびに新しい `DAO.Database` オブジェクトのインスタンスをカレントデータベースのワークスペース上に生成する。これをループ内で何度も呼び出すと、Accessの内部メモリ(Jet/ACEエンジンキャッシュ)が肥大化し、最悪の場合メモリー不足エラーを引き起こす。
原則として、`CurrentDb` の戻り値は必ずローカル変数(`Dim db As DAO.Database`)に一度だけキャッシュし、プロシージャ終了時に確実に `Set db = Nothing` で解放すること。
2. リンクテーブル(ODBC/別Accessファイル)における注意点
`AllTables` は、ローカルテーブルだけでなく、他データベースからのリンクテーブルも捕捉する。
しかし、「テーブル名は存在するが、接続先のバックエンドデータベースがネットワーク切断等で応答しない」というケースでは、`AllTables` は `True` を返すものの、実データを取得しようとした瞬間にODBCエラー(あるいはファイル未検出エラー)が発生する。
真に堅牢なシステムを構築する場合、`AllTables` で存在確認を行った 後、DAOの `TableDefs(n).Connect` プロパティを走査し、バックエンドの生存確認(あるいはタイムアウト制御)を組み合わせる多層防御(ディフェンス・イン・ディプス)が求められる。
—
結び
VBAは、しばしば「おもちゃの言語」と揶揄される。しかし、それは書く人間の技量に依存しているに過ぎない。
オブジェクトのライフサイクルを理解し、ランタイムのエラー機構に依存した怠惰なコードを排し、メタデータを巧みに操ることで、Access VBAは企業の基幹を支える強靭なエンジンへと生まれ変わる。
「動けばいい」の時代は終わった。
エラーを予測し、コントロールし、美しく优雅にフォールバックさせる――これこそが、真のプロフェッショナルエンジニアリングである。
