【実務・中級編】QueryDefを用いた「一時テーブル」の動的作成とクリーンアップ処理 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:QueryDefによる「一時テーブル」の動的生成とクリーンアップの極意

開発現場でよく見かける光景がある。
「複雑な帳票集計のために、とりあえず `CurrentDb.Execute “SELECT INTO tmp_Report FROM …”` で一時テーブルを作り、処理が終わったら放置。あるいはエラーハンドリングの途中で落ちて、次回起動時に『テーブルが既に存在します』というエラーでシステムが停止する」

――断言しよう。このアプローチをとっている時点で、そのAccessアプリケーションは「時限爆弾」を抱えている。

Access(Jet/ACEエンジン)において、場当たり的な物理テーブルの作成と削除は、データベースの肥大化(Bloat)、システム資源の枯渇、そしてマルチユーザー環境における競合(Locker)の元凶となる。

今回は、業務自動化のプロフェッショナルとして、`QueryDef`オブジェクトを駆使した安全かつ高速な動的データセットの構築と、例外が起きようとも確実に資源を解放するクリーンアップの設計思想を伝授する。

—

なぜ `SELECT INTO` による物理テーブル作成は悪手なのか?

多くの開発者がやりがちな `INTO` 句を使ったテーブル生成には、致命的な構造的欠陥がある。

1. システムの肥大化(Bloat)
テーブルの作成と削除を繰り返すと、Accessの内部ファイル(.accdb)に空き領域の断片化が発生し、ファイルサイズが爆発的に膨れ上がる。
2. 排他制御と競合
複数人が同時にその機能を使う場合、同じ名前のテーブルを作ろうとして確実に衝突する。
3. トランザクションとセキュリティ
システム領域に不要なゴミテーブルが残ることで、セキュリティ上のリスクや予期せぬクエリの誤作動を招く。

解決策:一時「QueryDef」と「ローカル一時テーブル(#)」の使い分け

真に堅牢なアーキテクチャでは、永続的な物理テーブルを作らない。
処理のライフサイクルに合わせてメモリ上、あるいはセッション内だけで完結する仕組みを作る。これに最も適しているのが、名前を付けない(あるいは明示的に管理する)`QueryDef`オブジェクトである。

—

実装パターン:QueryDefを用いた安全な動的データセット構築

以下のコードは、単に動的SQLを実行するだけではない。

  • トランザクション管理
  • 一意なセッション名による競合回避
  • 確実に実行されるクリーンアップ(Err.Numberに依存しない確実な解放)

これらを満たした、プロダクションクオリティのVBAコードだ。コピペしてそのまま現場の基幹システムに組み込んでほしい。

Option Compare Database
Option Explicit

Public Sub ExecuteReportProcess()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim strQueryName As String
Dim strSQL As String

‘ セッションごとにユニークなクエリ名を生成し、マルチユーザー環境での競合を完全になくす
strQueryName = “qdef_TempReport_” & Format(Now, “yyyymmddHHNNSS”) & “_” & Int(Rnd 10000)

Set db = CurrentDb()

On Error GoTo ErrorHandler

‘ 1. 動的SQLの構築(パラメータや複雑な結合を含むクエリを定義)
strSQL = “SELECT T_Orders.CustomerID, Sum(T_Orders.Amount) AS TotalAmount ” & _
“FROM T_Orders ” & _
“WHERE T_Orders.OrderDate >= #2023-01-01# ” & _
“GROUP BY T_Orders.CustomerID;”

‘ 2. QueryDefオブジェクトの動的作成(永続保存せず、QueryDefsコレクションに一時登録)
Set qdf = db.CreateQueryDef(strQueryName, strSQL)

‘ 3. レポートや次の処理へこのQueryDefの名前を引き渡す
‘ 例: Reports!Rpt_Sales.RecordSource = strQueryName
MsgBox “一時クエリ [” & strQueryName & “] を正常に生成しました。”, vbInformation, “処理成功”

‘ — ここに実際のレポート出力処理などが入る —

CleanUp:
‘ 4. 【最重要】エラーの有無に関わらず、必ずQueryDefを削除してリソースを解放する
On Error Resume Next
If Not qdf Is Nothing Then
db.QueryDefs.Delete strQueryName
End If
On Error GoTo 0

Set qdf = Nothing
Set db = Nothing
Exit Sub

ErrorHandler:
‘ 異常系ハンドリング
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp

End Sub

—

チーフアーキテクトが教える、現場で活きる3つの極意

1. `QueryDefs.Delete` の罠と `On Error Resume Next` の正しい使い方

VBAにおけるオブジェクト解放でやりがちなミスが、「削除対象が存在するか確認せずに `Delete` を叩いてエラーになる」こと。
クリーンアップセクション(`CleanUp:`)にジャンプする際、途中でエラーが起きていると、どこで落ちたかによって `qdf` オブジェクトがインスタンス化されているかどうかが曖昧になる。
そのため、削除処理の前には必ず `On Error Resume Next` を挟み、存在しない場合の例外を無害化するのが、プロの防衛的プログラミング(Defensive Programming)だ。

2. マルチユーザー(共有環境)への配慮

複数人が同時にAccessファイル(バックエンド、あるいは単一ファイル構成)を利用する場合、固定のクエリ名(例: `tmpQuery`)を使うと、Aさんが実行中にBさんが同じ名前を作ろうとして「実行時エラー ‘3012’: 指定した名前のクエリは既に存在します」を引き起こす。
先ほどのコードのように、`Format(Now, “yyyymmddHHNNSS”)` に加え、乱数(`Rnd`)を組み合わせたタイムスタンプベースの動的命名規則を採用することで、数千人が同時にアクセスしても絶対に衝突しない構造を担保できる。

3. Accessの「Compact and Repair(最適化)」を不要にする設計

物理テーブルをバッチ処理で作りまくるシステムは、週に1回のデータベース最適化が必須になる。しかし、今回紹介した `QueryDef` による仮想ビュー・動的データセットの構築手法であれば、物理的なレコードの追加・削除を行わないため、Access特有のゴミファイル(容量肥大化)が蓄積しない。
「メンテンスフリーのAccessアプリ」を作るための、これが最も確実なアプローチである。

—

結びに代えて

Accessは、正しく使えば超高速なローカルソリューションであり、間違った設計をすれば一瞬で破綻するデリケートなプラットフォームだ。
「動けばいい」のコードから脱却し、リソースのライフサイクルを完全にコントロール下に置くこと。それこそが、現場の信頼を勝ち取るエンジニアの仕事である。

次の開発からは、物理テーブルの `INTO` 句を封印し、洗練された `QueryDef` のライフサイクル管理を実装してほしい。そのコードの美しさは、必ずシステムの安定性という結果で応えてくれるはずだ。

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