Access VBAで「UNION ALL」を動的に制する:極限のクエリ構築術
業務システム開発において、Accessの「UNION ALL」は諸刃の剣だ。複数のテーブルからデータを統合する際、安易にクエリをハードコーディングしていないか? 「テーブルが増えるたびにクエリを書き換える」ような設計は、技術的負債以外の何物でもない。
今回は、メンテナンス性を極限まで高めた「動的UNION ALL生成エンジン」を構築する知見を伝授する。
—
1. なぜ「静的クエリ」ではいけないのか
多くの開発者は、クエリデザイナで作成したSQLをそのままVBAに埋め込む。だが、以下のリスクを考慮しているか?
- 変更への脆弱性: テーブル構造や増設に対応できず、改修工数が肥大化する。
- デバッグの困難さ: 実行時の動的な条件付与が文字列連結で行われ、構文エラーの温床となる。
- パフォーマンスのブラックボックス化: 実行プランが最適化されないまま実行されるリスク。
我々が目指すべきは、「構造を抽象化し、実行時に堅牢なSQLを組み立てる」アーキテクチャだ。
—
2. 堅牢なSQL生成エンジンの設計指針
今回作成する関数 `GenerateUnionSQL` は、以下の設計原則に基づいている。
1. 配列引数による拡張性: `Variant`型の配列でテーブル名を渡すことで、テーブルの増減に即座に対応する。
2. パラメータクエリの活用: SQLインジェクションを防ぎ、実行プランのキャッシュ効率を高める(DAO.QueryDefの活用)。
3. エラーハンドリングの徹底: 生成されたSQLが妥当か、事前検証を行う。
—
3. 実装コード:汎用動的SQL生成モジュール
以下のコードを標準モジュールに配置し、即戦力として活用してほしい。
Option Compare Database
Option Explicit
‘ ==============================================================================
‘ 機能:指定されたテーブル配列をUNION ALLで結合するSQLを生成する
‘ 引数:arrTables – テーブル名の配列
‘ strWhere – 共通の抽出条件(WHERE句 ※WHEREを含めず記述)
‘ 戻り値:生成されたSQL文字列
‘ ==============================================================================
Public Function GenerateUnionSQL(arrTables As Variant, Optional strWhere As String = “”) As String
Dim i As Long
Dim strSQL As String
Dim strResult As String
‘ 配列の妥当性チェック
If Not IsArray(arrTables) Then Exit Function
For i = LBound(arrTables) To UBound(arrTables)
‘ 各テーブルから必要なカラムを取得(ではなく明示的に指定することを推奨)
strSQL = “SELECT FROM [” & arrTables(i) & “]”
‘ 条件付与
If Len(strWhere) > 0 Then
strSQL = strSQL & ” WHERE ” & strWhere
End If
strResult = strResult & strSQL
‘ 最後の要素以外にUNION ALLを付加
If i < UBound(arrTables) Then
strResult = strResult & " UNION ALL "
End If
Next i
GenerateUnionSQL = strResult
End Function
' --- 実行例:フォームやレポートのレコードソースを動的に切り替える場合 ---
Public Sub ApplyDynamicSource(targetForm As Form, tableList As Variant)
Dim sql As String
sql = GenerateUnionSQL(tableList, "Status = 'Active'")
' レコードソースの書き換え
targetForm.RecordSource = sql
End Sub
---
4. アーキテクトからの重要な忠告
この手法を運用する上で、必ず守るべき「3つの鉄則」がある。
① `SELECT ` の禁止
上記の例では汎用性を重視して “ を使用したが、本番環境では「必要なカラムのみを明示的に記述」すること。テーブルの列順が変わっただけでツールが崩壊するのを防ぐためだ。
② `QueryDef` での実行を優先せよ
単なる文字列としてSQLを扱うのではなく、`CurrentDb.CreateQueryDef` を使用して一時的にクエリを作成し、それを実行・参照する構成にすれば、Accessのクエリ最適化エンジンが正しく機能する。
③ 実行計画のキャッシュを意識する
Access(JET/ACEエンジン)はSQL文字列が完全一致した場合にクエリプランをキャッシュする。動的な条件(WHERE句)を生成する際は、可能な限りパラメータクエリ(`[Forms]![frmMain]![txtDate]` 等)をSQL内に埋め込み、文字列そのものを変化させない工夫が重要だ。
—
最後に:自動化の先にあるもの
このコードは単なる「文字列連結」ではない。テーブルという「データコンテナ」を抽象化し、VBAという「制御層」から分離するための第一歩だ。
コードを書くとき、常に自問自答してほしい。「これは半年後の自分が、何のドキュメントも見ずに修正できるコードか?」と。もし答えが「No」なら、それは負債である。
現場で困ったことがあれば、またいつでも聞きに来てほしい。エンジニアの戦いは、泥臭い実装の積み重ねの先にあるのだから。
