【実務・中級編】【実務】隠しテーブルやシステムテーブルをVBAで安全に操作する注意点 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:システムテーブル・隠しテーブルの安全な制御と実務的活用術

開発プロジェクトの現場で、こんな要望を受けたことはないだろうか。

  • 「現在データベース内に存在する全てのテーブル名とレコード数を一覧化したい」
  • 「フォームやクエリの設計変更履歴をVBAで動的に監査したい」
  • 「ユーザーが誤って触らないように、特定のテーブルを完全に隠蔽・保護したい」

これらを素朴に実装しようとして、`CurrentDb.TableDefs` をただループさせ、`.Attributes` プロパティの変更や `MSysObjects` への直接クエリ発行でハマった経験はないだろうか?

結論から言おう。Accessのシステムテーブルや隠しテーブルの制御を甘く見ると、「データベースの突然の破損」「不可解な実行時エラー」「セキュリティの穴」という致命傷を招く。

今回は、Accessの内部構造(Jet/ACEエンジン)のライフサイクルを知り尽くしたアーキテクトの視点から、システムテーブルを安全に掌握し、実務の業務効率化ツールへ昇華させるための極限の知見を伝授する。

1. なぜ素朴な実装は破綻するのか?(非効率な設計の排除)

多くの開発者が犯す最大の過ちは、「システムテーブル(`MSysObjects` 等)に対して通常のテーブルと同じ感覚でSELECT文を投げる」こと、そして「隠しテーブルのフラグ操作を場当たり的に行う」ことだ。

罠その1:`MSysObjects` への直クエリによる権限エラー

`MSysObjects`(Accessのシステムオブジェクトを管理するメタデータテーブル)は、デフォルトでは読み取り権限が厳格に制限されている。安易に `SELECT FROM MSysObjects` を実行すると、環境やセキュリティ設定(ワークグループやACCDE化など)によって「レコード読取権限がありません」というエラーが容赦なく飛んでくる。

罠その2:`TableDefs` の属性(Attributes)変更のタイミングミス

テーブルを「隠しテーブル」にするには、`TableDef.Attributes` に `dbHiddenObject` を付加する。しかし、これを対象テーブルが開いている状態や、トランザクションのスコープ内で実行すると、エラーが発生するか、最悪の場合はカタログ情報が破損し、データベース全体がオープンできなくなる。

堅牢な設計の鉄則

1. メタデータへのアクセスは DAO (`TableDefs` / `Properties`) を優先する(ADOよりもAccess固有のオブジェクトモデルの方がエンジンレベルで安全)。
2. システムテーブルへのクエリは必要最小限にし、エラーハンドリングを必ず包む。
3. 隠し属性の変更前には、必ずオブジェクトの排他制御(閉じた状態の保証)を行う。

2. 実務で使えるプロダクションコード:安全なテーブル監査と隠蔽処理

ここからは、現場で即座にコピー&ペーストして使えるプロダクションコードを提示する。
このコードは、以下の要件を満たしている。

  • システムテーブル(`MSys %>` など)を安全に除外、あるいは適切に識別する。
  • ユーザー定義の隠しテーブルを安全に作成・属性付与する。
  • 予期せぬエラーに対する厳格なロールバック・例外処理。

モジュールコード

Option Compare Database
Option Explicit

‘ =================================================================================
‘ módulo名: modTableGovernance
‘ 概要 : テーブルのメタデータ取得、隠し属性の安全な制御を行うプロフェッショナルモジュール
‘ =================================================================================

‘ 隠し属性フラグ (DAO.TableDefAttributes)
Private Const TABLE_ATTR_HIDDEN As Long = &H1

/

  • データベース内の全テーブル(システムテーブルを除く)をイテレートし、
  • ログテーブルへステータスを出力する実務的サンプル

/
Public Sub AuditUserTables()
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim ws As DAO.Workspace

Set db = CurrentDb
Set ws = DBEngine.Workspaces(0)

On Error GoTo ErrorHandler

Debug.Print “=== テーブル監査開始: ” & Now & ” ===”

For Each tdf in db.TableDefs
‘ 1. システムテーブルおよびリンクテーブルを安全に判定してスキップ
‘ MSysで始まるもの、および連結テーブル(AttributesにdbAttachedTable等が含まれる)を排除
If Not IsSystemOrLinkedTable(tdf) Then

Debug.Print “テーブル名: ” & tdf.Name & _
” | レコード数(推定): ” & GetRecordCountSafe(tdf.Name) & _
” | 隠し属性: ” & CBool((tdf.Attributes And TABLE_ATTR_HIDDEN) = TABLE_ATTR_HIDDEN)

End If
Next tdf

Debug.Print “=== テーブル監査完了 ===”
Exit Sub

ErrorHandler:
MsgBox “テーブル監査中に致命的なエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
‘ 実務ではここでログ出力基盤へエラースタックを送信する処理を記述
End Sub

/

  • 指定したテーブルの隠し属性(Hidden)を安全に切り替える
  • @param tableName 対象テーブル名
  • @param makeHidden True: 隠しす / False: 表示する

/
Public Sub SetTableHiddenAttribute(ByVal tableName As String, ByVal makeHidden As Boolean)
Dim db As DAO.Database
Dim tdf As DAO.TableDef

Set db = CurrentDb

On Error GoTo SafeExit

‘ テーブルが存在するかチェック
If Not TableExists(tableName) Then
Err.Raise 3265, , “指定されたテーブルが存在しません: ” & tableName
End If

Set tdf = db.TableDefs(tableName)

‘ 【重要】テーブルが開いていると属性変更できないため、強制的に閉じる
DoCmd.Close acTable, tableName, acSaveNo

‘ 属性の書き換え(ビット演算による安全なトグル)
If makeHidden Then
tdf.Attributes = tdf.Attributes Or TABLE_ATTR_HIDDEN
Else
tdf.Attributes = tdf.Attributes And Not TABLE_ATTR_HIDDEN
End If

‘ Accessのナビゲーションウィンドウを強制リフレッシュして変更を反映
Application.RefreshDatabaseWindow

Debug.Print “テーブル [” & tableName & “] の隠し属性を ” & makeHidden & ” に変更しました。”
Exit Sub

SafeExit:
MsgBox “隠し属性の変更に失敗しました: ” & Err.Description, vbExclamation, “制御エラー”
End Sub

‘ — ヘルパー関数群 —

Private Function IsSystemOrLinkedTable(ByVal tdf As DAO.TableDef) As Boolean
‘ システムテーブル (MSys… または ~ で始まる) かどうか
If Left$(tdf.Name, 4) = “MSys” Or Left$(tdf.Name, 1) = “~” Then
IsSystemOrLinkedTable = True
Exit Function
End If

‘ リンクテーブル (dbAttachedTable または dbAttachedODBC) かどうか
If (tdf.Attributes And dbAttachedTable) = dbAttachedTable Or _
(tdf.Attributes And dbAttachedODBC) = dbAttachedODBC Then
IsSystemOrLinkedTable = True
Exit Function
End If

IsSystemOrLinkedTable = False
End Function

Private Function TableExists(ByVal tableName As String) As Boolean
Dim tdf As DAO.TableDef
On Error Resume Next
Set tdf = CurrentDb.TableDefs(tableName)
TableExists = (Err.Number = 0)
On Error GoTo 0
End Function

Private Function GetRecordCountSafe(ByVal tableName As String) As Long
On Error Resume Next
‘ 巨大なテーブルに対してDCountを使うと重いため、RecordCountプロパティをまず試す
‘ (ただしダイナセット等を開いていないTableDefのRecordCountは当てにならない場合があるためフォールバック用意)
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset(“SELECT COUNT(1) FROM [” & tableName & “]”, dbOpenSnapshot)
If Err.Number = 0 Then
GetRecordCountSafe = rs.Fields(0).Value
rs.Close
Else
GetRecordCountSafe = -1
End If
On Error GoTo 0
End Function

3. コードの解説:なぜこの実装が「極限の知見」なのか

1. 排他制御の徹底 (`DoCmd.Close`)
ユーザーがUI上でテーブルを開きっぱなしにしている状態で `TableDefs.Attributes` を書き換えようとすると、Accessは容赦なく実行時エラーを吐く。本コードでは属性変更の直前に明示的にテーブルをクローズし、さらに変更後に `Application.RefreshDatabaseWindow` を呼ぶことで、UIとの整合性を完全に担保している。

2. ビット演算子による属性管理
Accessの `Attributes` プロパティはビットフラグの集合体である。単純に代入するのではなく、`Or` や `And Not` を用いたビット演算を行うことで、他の重要なシステム属性(リプリケーション関連やテンポラリ属性など)を破壊せずに、安全に `dbHiddenObject` だけをトグルさせることができる。

3. 厳格なシステム・リンク判定
単に `MSys` プレフィックスを見るだけでなく、テンポラリテーブル(`~`で始まるもの)や外部ODBC・他ファイルへのリンクテーブルを排除している。これにより、バックエンド側の構成変更に強い堅牢なツール設計が可能となる。

4. まとめ:実務におけるシステムテーブル制御の心得

Access VBAを用いた本格的な業務自動化・ツール開発において、データベースの「内部構造」に踏み込むアプローチは、時に開発者の強力な武器となり、時に諸刃の剣となる。

システムテーブルや隠しテーブルを操作する際は、以下の鉄則を常に胸に刻んでほしい。

  • 直接SQLでMSysObjectsを叩くのは最終手段。極力DAOのメタデータAPI(`TableDefs`, `Properties`)を経由せよ。
  • オブジェクトの状態(オープン状態)を常に意識し、変更前には必ず環境をクリーンな状態に誘導せよ。
  • 変更後は必ずデータベースウィンドウをリフレッシュし、ユーザーインターフェースと内部モデルの乖離を防げ。

このレベルの設計思想を身につければ、単なる「マクロ作成者」から、組織の基幹を支える「真のAccessアーキテクト」へとステップアップできるはずだ。次の開発案件から、ぜひこのコードと知見を役立ててほしい。

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