【実務・中級編】サブクエリを多用する複雑なSQLをQueryDefで動的に組み立てるコツ – Access VBA解析バイブル

スポンサーリンク

複雑怪奇なサブクエリを支配する:Access VBA「QueryDef」動的構築の極意

Access開発の現場で、最も「負債」を生み出しやすい場所がどこか知っているか? それは、VBAの中に埋め込まれた「巨大で無機質なSQL文字列の塊」だ。

「とりあえず動けばいい」と連結演算子(`&`)で繋がれたSQLは、数ヶ月後の君を地獄に突き落とす。括弧の対応、改行の欠落、デバッグ時の視認性の低さ。これらは単なるコードの汚れではない。バグの温床そのものだ。

今回は、サブクエリを多用する複雑なロジックを、堅牢かつ「メンテナンス可能な」SQLに昇華させるための極限のアーキテクチャを伝授する。

—

1. なぜ「文字列連結」は敗北するのか

多くのエンジニアは、SQLを1行の長い文字列として扱おうとする。これが間違いの元だ。
サブクエリが階層化するほど、人間は「どこでクエリが閉じているか」を判別できなくなる。

解決策は明確だ。 SQLを「データ」として扱い、Builderパターンに近いアプローチで「行単位」でスタック(構築)することである。

2. メンテナンス性を最大化する「SQL Builder」の設計思想

以下のコードを見てほしい。これが、複雑なSQLを制御するための「プロダクション・グレード」の定石だ。

‘ 複雑なSQLを動的に構築するためのモジュール構成例
Public Function GenerateComplexQuery(ByVal targetDate As Date) As String
Dim sql As Collection
Set sql = New Collection

‘ 1. インデントを意識した行単位の構築
sql.Add “SELECT T1.ID, T1.Name, T2.TotalAmount”
sql.Add “FROM (SELECT ID, Name FROM T_Master WHERE Active = True) AS T1”
sql.Add “INNER JOIN (”
sql.Add ” SELECT ParentID, SUM(Amount) AS TotalAmount”
sql.Add ” FROM T_Details”
sql.Add ” WHERE TransDate >= #” & Format(targetDate, “yyyy/mm/dd”) & “#”
sql.Add ” GROUP BY ParentID”
sql.Add “) AS T2 ON T1.ID = T2.ParentID”
sql.Add “ORDER BY T2.TotalAmount DESC;”

‘ 2. Collectionを改行コードで結合して一気に生成
GenerateComplexQuery = JoinCollection(sql, vbCrLf)
End Function

‘ 汎用ヘルパー:保守性の肝
Private Function JoinCollection(col As Collection, delimiter As String) As String
Dim v As Variant
Dim result As String
For Each v In col
result = result & v & delimiter
Next v
JoinCollection = Left(result, Len(result) – Len(delimiter))
End Function

3. この設計が「堅牢」である理由

  • 視認性の確保: SQLが`Collection`に格納されているため、コード上でインデントを反映できる。SQLの階層構造がそのままコードの見た目に現れる。
  • デバッグの容易さ: `Debug.Print` で出力した際、SQLエディタでそのまま貼り付けて実行可能な形式(整形された状態)で出力される。
  • 分離の原則: SQLの「ロジック(文字列構築)」と「実行(QueryDefへの適用)」を切り離しているため、テストが極めて容易だ。

4. QueryDefでの実行:隠れた罠を避ける

動的にSQLを組み立てた後、それを `QueryDef` に適用して保存・実行する際は、以下の「作法」を守らなければならない。

Public Sub UpdateReportQuery(sqlStr As String)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef

Set db = CurrentDb

‘ 既存のクエリ定義が存在するか確認し、なければ作成する
On Error Resume Next
Set qdf = db.QueryDefs(“qry_DynamicReport”)
If Err.Number <> 0 Then
Set qdf = db.CreateQueryDef(“qry_DynamicReport”, sqlStr)
Else
qdf.SQL = sqlStr ‘ SQLを更新
End If
On Error GoTo 0

‘ クエリを閉じる(リソースリークを防ぐ)
qdf.Close
Set qdf = Nothing
End Sub

注意点:パフォーマンスとリソース管理

  • QueryDefの再利用: SQLを毎回 `CreateQueryDef` で作り直すのはオーバーヘッドが大きい。一度作成したオブジェクトを `qdf.SQL = …` で上書き更新するのが定石だ。
  • パラメーターの扱い: 動的に値を埋め込む際、文字列連結(`Format`関数等)は注意が必要だ。日付や数値ならまだしも、ユーザー入力値を含む場合は必ず「SQLインジェクション」を考慮し、適切にクォーテーションをエスケープすること。あるいは、`Parameters` コレクションを使用する設計にすべきだ。

—

プロフェッショナルへのアドバイス

君が書くそのコードは、君がいなくなった後も誰かが保守することになる。
「動けばいい」というコードは、数ヶ月後に「触りたくないゴミ」へと姿を変える。

SQLを文字列の塊としてではなく、階層構造を持つ「設計物」として扱うこと。
今回紹介した `Collection` を使った構築手法は、小規模なツールから大規模な基幹システムまで通用する、最も汎用性が高く、かつ最も「エンジニアの矜持」を感じさせるアプローチだ。

さあ、汚れた文字列連結を捨て、構造的な構築を始めよう。君のAccessが、真の業務自動化エンジンへと進化するはずだ。

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