【テクニカル・上級編】Application.CurrentData.AllTablesを使用した、存在しないテーブルへのアクセス回避 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:`CurrentData.AllTables`による存在証明とロバストなシステム設計

レガシーシステムの維持において、最もエンジニアの精神を摩耗させる瞬間は何か。
それは、夜間バッチやユーザーの気まぐれな操作によって、突如として発生する「実行時エラー 3011:Microsoft Access データベース エンジンはオブジェクト ‘XXX’ を見つけられませんでした」のログを見た瞬間ではないか。

動的な一時テーブルの生成、外部基幹システムからのODBCリンクテーブルの切断、あるいはデプロイ時の不整合。Access VBAのコードベースにおいて、「テーブルが存在するかどうか」を事前に担保せずにいきなりクエリを発行したり、レコードセットを開いたりする設計は、時限爆弾を抱えて走るようなものだ。

今回は、エラーハンドリング(`On Error Resume Next`という悪しき逃げ道)に頼らず、Accessのオブジェクトモデルの深部を突いて、極限まで堅牢な存在チェックを実現するアーキテクチャを提示する。

1. なぜ `On Error Resume Next` は悪手なのか

多くの初級・中級プログラマは、テーブルの存在確認を行う際に以下のようなコードを書く。

‘ 【アンチパターン】絶対に真似してはならないコード
Function IsTableExist_Bad(tableName As String) As Boolean
On Error Resume Next
Dim db As DAO.Database
Set db = CurrentDb
Dim td As DAO.TableDef
Set td = db.TableDef(tableName) ‘ 存在しないとここでエラー

If Err.Number = 0 Then
IsTableExist_Bad = True
Else
IsTableExist_Bad = False
End If
On Error GoTo 0
End Function

このアプローチがなぜプロフェッショナルとして許されないのか。
1. VBAの実行時エラーハンドリング機構のオーバーヘッド: エラー発生時のコンテキスト切り替えは重い。
2. トラップ漏れの危険性: `Err.Number`のクリアを忘れた場合、別のエラーを誤認する。
3. DAOコレクションの重み: `TableDef`コレクション全体をメモリ上に列挙・走査するコストは、大規模なバックエンドデータベース(SQL Server等)をリンクしている環境において無視できないネットワーク・I/O負荷を生む。

真のエンジニアは、エラーが発生する前に「知る」のではなく、「データ構造のメタデータに直接問う」アプローチをとる。

2. `Application.CurrentData.AllTables` の真価

Accessオブジェクトモデルにおいて、`CurrentData`プロパティは、DAOのレイヤーよりも軽量な、Accessデータベースのコンテナ情報(テーブルやクエリの存在有無)をラップしたカプセル化されたコレクションを返す。

その中でも `AllTables` コレクションは、実際にテーブルを開くことなく、また物理的なスキーマを深くロードすることなく、名前ベースで高速に存在確認を行うための最適解である。

`AllTables` コレクションの構造的優位性

  • 遅延評価と軽量性: DAOの`TableDef`コレクションとは異なり、ローカル・リンクを問わず「定義されている名前のリスト」をメモリ上で高速にヒットさせる。
  • ADO/DAOの垣根を超えた一貫性: Accessのアプリケーションスコープに直結しているため、カレントデータベースの状態をミリ秒単位で正確に捉える。

3. 実装:極限まで最適化された存在チェック関数

以下に、実務のエンタープライズ環境でそのまま使用できる、堅牢性とパフォーマンスを極限まで高めたテーブル存在確認・リンク切れ検知モジュールを提示する。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ módulo名: modTableValidator
‘ 概要 : Accessデータベース内のテーブル存在および接続状態を極限の効率で検証する
‘ 著者 : Chief Architect
‘ ==============================================================================

/

  • 指定されたテーブル名がカレントデータベースに存在し、かつアクセス可能か判定する。
  • @param ByVal targetTableName As String – 検索対象のテーブル名
  • @return Boolean – 存在する場合はTrue、それ以外はFalse

/
Public Function IsTableAccessible(ByVal targetTableName As String) As Boolean
Dim obj As AccessObject
Dim isExist As Boolean

isExist = False

‘ CurrentData.AllTables から指定名義のオブジェクトを安全に探索
‘ ※大文字小文字を区別しないAccessの仕様に準拠
For Each obj In Application.CurrentData.AllTables
If StrComp(obj.Name, targetTableName, vbTextCompare) = 0 Then
isExist = True
Exit For
End If
Next obj

If Not isExist Then
IsTableAccessible = False
Exit Function
End If

‘ さらに、リンクテーブル等の場合に接続が生きているか(切断されていないか)を検証
‘ 物理的に開けるかどうかをDAOの軽量な接続テストで担保する
IsTableAccessible = ValidateConnection(targetTableName)

End Function

/

  • テーブルオブジェクトに対して実際に軽量なクエリを発行し、接続生存確認を行う。
  • @param ByVal tableName As String – 検証するテーブル名
  • @return Boolean – 接続が有効な場合はTrue

/
Private Function ValidateConnection(ByVal tableName As String) As Boolean
Dim db As DAO.Database
Dim rs As DAO.Recordset

On Error GoTo ErrorHandler

Set db = CurrentDb()

‘ 0件のレコードセットを開くことで、ネットワークやODBCドライバの疎通を強制確認する
‘ TOP 1 構造により、データ転送量を極限までゼロに抑える
Set rs = db.OpenRecordset(“SELECT TOP 1 FROM [” & tableName & “];”, dbOpenSnapshot, dbSQLPassThrough)

ValidateConnection = True
GoTo CleanUp

ErrorHandler:
‘ 3011, 3151 (ODBC接続失敗), その他のエンジンエラーを捕捉
ValidateConnection = False

CleanUp:
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
‘ CurrentDb()が返すローカルデータベース参照の明示的破棄(メモリリーク防止の鉄則)
If Not db Is Nothing Then
Set db = Nothing
End If
On Error GoTo 0
End Function

4. チーフアーキテクトが解説するメモリ最適上の要点

上記のコードには、レガシーAccess開発でありがちな「メモリリーク」や「リソース解放漏れ」を防ぐための高度な知見が組み込まれている。

① `CurrentDb()` のキャッシングと即時解放

`CurrentDb`関数は呼び出すたびに新しい`DAO.Database`オブジェクトのインスタンスをメモリ上に生成する。これをローカル変数 `db` に受けて使い回し、処理の最後で必ず `Set db = Nothing` によって参照カウンタをデクリメントしている。
これを怠ると、長時間のバッチ処理においてAccessのメモリフットプリントが肥大化し、最終的に「リソース不足(Error 3048)」を引き起こす。

② `dbOpenSnapshot` と `dbSQLPassThrough` の併用によるI/O最小化

リンクテーブル(SQL ServerやOracle等)の存在確認において、単なるテーブル参照を行うと、ドライバの仕様によってはメタデータの全件フェッチが発生する場合がある。
`SELECT TOP 1 FROM …` を `dbOpenSnapshot`(更新不可・ローカルキャッシュなし)かつパススルー的な挙動で開くことにより、データベースサーバー側での最小限の処理コストで接続生存確認(Heartbeat)を完了させることができる。

5. 実運用における適用シナリオ

この `IsTableAccessible` 関数をシステムの起動時や、重いバッチ処理のルーチンの冒頭に組み込む。

Public Sub ExecuteEnterpriseBatch()
Const TARGET_TABLE As String = “M_External_Link_Data”

‘ 1. テーブルの存在と接続性を事前検証
If Not IsTableAccessible(TARGET_TABLE) Then
MsgBox “致命的なエラー: 外部連携テーブル [” & TARGET_TABLE & “] にアクセスできません。” & vbCrLf & _
“ネットワーク接続またはODBC DSNの設定を確認してください。”, _
vbCritical + vbOKOnly, “システム異常終了”
Exit Sub
End Sub

‘ 2. 安全に本処理へ移行
‘ …(ここに実際のデータ処理を記述)…

End Sub

総括

VBAは「おもちゃの言語」と揶揄されることがある。しかし、それは書く人間の技量が低い場合の話に過ぎない。
メモリのライフサイクルを支配し、オブジェクトモデルの裏側にあるC++ベースのCOMコンポーネントの挙動までを想像してコードを組み上げる時、Access VBAはどんな基幹系システムにも匹敵する堅牢な要塞へと変貌する。

エラーが起きてから慌てて対処する受動的なプログラミングは今日で終わりにしよう。
`CurrentData.AllTables` を網羅的に使いこなし、システムに「予期せぬ障害」の余地を与えないこと。それこそが、プロフェッショナルエンジニアの仕事である。

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