【実務・中級編】フォームの「RecordSource」をVBAで動的に書き換える検索画面の構築 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:動的RecordSource制御による「真に実用的な」検索画面の設計

こんにちは。開発プロジェクトの現場で、幾多のAccessスパゲッティコードを鎮圧し、高速かつ堅牢な業務システムへと蘇らせてきたチーフアーキテクトだ。

Accessを使った業務アプリケーション開発において、避けて通れないのが「検索画面」の実装である。
「フォームにテキストボックスを並べ、ボタンを押したら条件に合うレコードだけを表示したい」
この要求に対し、未熟なプログラマーはすべてのレコードをバインドした重いフォームを用意し、その上で `Filter` プロパティや `ServerFilter` をこねくり回す。あるいは、レコードごとにクエリを動的に書き換えようとしてファイルサイズを肥大化させる。

結論から言おう。大規模なデータや複雑な条件を扱う検索画面において、フォームの `RecordSource`(レコードソース)をVBAで動的に書き換える手法こそが、パフォーマンスと保守性のバランスを極限まで高める唯一の解である。

今回は、現場のプロが実践している「バグの起きない堅牢な動的SQL構築」と「Accessオブジェクトモデルの急所を押さえた設計手法」を授けよう。

1. なぜ「フィルター処理」ではなく「RecordSourceの動的書き換え」なのか?

初心者向けの手法として `Me.Filter` と `Me.FilterOn = True` を使った絞り込みがよく紹介される。しかし、実務の現場ではこれは悪手となるケースが多い。

  • 転送量の無駄(パフォーマンスの劣化): `Filter` は、結局テーブルやクエリの全レコードをローカル(またはクライアント側)に読み込んだ後でフィルタリングを行っている。数万件のレコードがある場合、ネットワークやメモリを無駄に消費する。
  • SQLの表現力の限界: 複数テーブルのJOIN(結合)や、集計関数(GROUP BY)、サブクエリを用いた複雑な検索条件を `Filter` プロパティだけで表現することは極めて困難、あるいは不可能だ。

一方、`RecordSource` に適切なWHERE句を持つ軽量なSQLを直接流し込むアプローチであれば、データベースエンジン側で必要なレコードだけを抽出し、最小限のコストでフォームに描画できる。 これがプロの選択だ。

2. 動的SQL構築における「3つの大罪」と対策

VBAでSQLを組み立てる際、多くの開発者が以下の罠に落ち、後日「検索できない」「謎のエラーが出る」「SQLインジェクションの危険がある」といった障害を引き起こす。

1. 「&」つなぎのスパゲッティコード: 文字列連結が多すぎて、どこにシングルクォーテーション(`’`)が必要なのか分からなくなる。
2. Null値の完全無視: 検索条件の入力欄が空欄(Null)のとき、SQLに `WHERE 担当者 = ` のように値が抜け落ちてSyntax Error(構文エラー)になる。
3. 型の不一致とエスケープ漏れ: 文字列型には `’` が必要だが、数値型や日付型にそのまま入れるとバグる。特に日付の書式(`#yyyy/mm/dd#`)や、文字列内の半角シングルクォーテーションのエスケープを怠る。

これらを完全に排除するため、当チームでは「条件パーツの配列結合方式」を標準採用している。

3. 【プロダクションコード】堅牢な検索画面の実装例

以下のコードは、実際の現場でそのままコピー&ペーストし、即座にプロダクション品質で稼働させることができるモジュールだ。

前提として、検索用フォームに以下のコントロールが存在するものとする。

  • `txtCustomerName` (顧客名:テキストボックス・部分一致)
  • `cmbStatus` (ステータス:コンボボックス・完全一致)
  • `txtDateFrom` / `txtDateTo` (受注日範囲:テキストボックス・期間指定)
  • `lstResults` または サブフォーム (結果表示用。今回はメインフォーム自身のRecordSourceを書き換える設計とする)

Option Compare Database
Option Explicit

‘=============================================================================
‘ フォーム名: frmSearch
‘ 処理概要: 検索条件を動的に構築し、RecordSourceを書き換えて高速検索を行う
‘=============================================================================

Private Sub btnSearch_Click()
On Error GoTo ErrorHandler

Dim strSQL As String
Dim strWhere As String
Dim varCriteria As Variant

‘ 条件を格納する動的配列の初期化
varCriteria = Array()

‘————————————————————————-
‘ 1. 検索条件の収集とバリデーション(条件パーツの生成)
‘————————————————————————-

‘ 条件1: 顧客名(部分一致)
If Not IsNull(Me.txtCustomerName.Value) And Trim(Me.txtCustomerName.Value) <> “” Then
‘ SQLインジェクション対策として、入力値内のシングルクォーテーションをエスケープ
Dim safeName As String
safeName = Replace(Me.txtCustomerName.Value, “‘”, “””)

PushCriteria varCriteria, “CustomerName LIKE ‘” & safeName & “‘”
End If

‘ 条件2: ステータス(数値型・完全一致の例)
If Not IsNull(Me.cmbStatus.Value) Then
PushCriteria varCriteria, “StatusID = ” & Me.cmbStatus.Value
End If

‘ 条件3: 受注日(範囲指定:開始日)
If Not IsNull(Me.txtDateFrom.Value) Then
If Not IsDate(Me.txtDateFrom.Value) Then
MsgBox “開始日の形式が正しくありません。”, vbExclamation, “入力エラー”
Me.txtDateFrom.SetFocus
Exit Sub
End If
‘ Access SQL(Jet/ACE)では日付は # で囲む
PushCriteria varCriteria, “OrderDate >= #” & Format(Me.txtDateFrom.Value, “yyyy/mm/dd”) & “#”
End If

‘ 条件4: 受注日(範囲指定:終了日)
If Not IsNull(Me.txtDateTo.Value) Then
If Not IsDate(Me.txtDateTo.Value) Then
MsgBox “終了日の形式が正しくありません。”, vbExclamation, “入力エラー”
Me.txtDateTo.SetFocus
Exit Sub
End If
‘ 終了日は時刻まで考慮して当日の23:59:59にするか、< #翌日# にするのが定石 PushCriteria varCriteria, "OrderDate < #" & Format(DateAdd("d", 1, Me.txtDateTo.Value), "yyyy/mm/dd") & "#" End If '------------------------------------------------------------------------- ' 2. SQLの組み立て '------------------------------------------------------------------------- ' ベースとなるSQL(パフォーマンスを考慮し、必要な列とインデックスが効くテーブル・クエリを指定) strSQL = "SELECT ID, CustomerName, StatusName, OrderDate, TotalAmount " & _ "FROM qryOrderSearchBase" ' 条件が1つ以上存在する場合は WHERE 句を結合 If UBound(varCriteria) >= 0 Then
strSQL = strSQL & ” WHERE ” & Join(varCriteria, ” AND “)
End If

‘ ソート順の付与(必要に応じて)
strSQL = strSQL & ” ORDER BY OrderDate DESC;”

‘ デバッグ用(イミディエイトウィンドウに発行SQLを出力。開発時の必須作法)
Debug.Print “Generated SQL: ” & strSQL

‘————————————————————————-
‘ 3. レコードソースの適用と再描画
‘————————————————————————-
‘ フォームのRecordSourceに動的SQLを代入
Me.RecordSource = strSQL

‘ フォームを再クエリして表示を更新
Me.Requery

‘ 検索結果件数の表示制御(フッター等に txtResultCount がある想定)
If Me.Recordset.RecordCount = 0 Then
MsgBox “条件に一致するデータはありません。”, vbInformation, “検索結果”
End If

Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “システムエラー”
End Sub

‘=============================================================================
‘ 補助プロシージャ: 動的配列に条件文字列を追加するヘルパー
‘=============================================================================
Private Sub PushCriteria(ByRef arr As Variant, ByVal condition As String)
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr)) = condition
End Sub

‘=============================================================================
‘ クリアボタン押下時の処理: 検索条件を初期化し、全件(または初期件数)に戻す
‘=============================================================================
Private Sub btnClear_Click()
Me.txtCustomerName.Value = Null
Me.cmbStatus.Value = Null
Me.txtDateFrom.Value = Null
Me.txtDateTo.Value = Null

‘ 初期状態のレコードソースに戻す(全件表示が重い場合は抽出件数ゼロのSQLを指定する配慮も重要)
Me.RecordSource = “SELECT TOP 100 ID, CustomerName, StatusName, OrderDate, TotalAmount FROM qryOrderSearchBase ORDER BY OrderDate DESC;”
Me.Requery
End Sub

4. チーフアーキテクトが教える、現場で生き残るための実装の急所

上記のコードをプロジェクトに組み込む際、さらにシステムを堅牢にするための実践的な知見を共有しよう。

① 「全件表示」の罠に備えよ

データ量が数十万件を超える巨大なテーブルに対して、検索条件未入力のまま「全件表示」のSQLを流すと、Access(ACEエンジン)はフリーズするか、クライアントPCのメモリを食い潰す。
クリアボタンや初期ロード時には、上記のコード例のように `SELECT TOP 100` などの上限を設けるか、「検索条件を1つ以上指定してください」というバリデーションを挟む設計が、業務システムのクラッシュを防ぐ最大の防衛策となる。

② クエリのキャッシングとパフォーマンス

フォームの `RecordSource` に直接長大なSQLを書き込む手法は非常に強力だが、あまりに複雑なJOINや演算をコード内で組み立てると、Jet/ACEデータベースエンジンがクエリプランを毎回最適化(コンパイル)するため、わずかに行き詰まることがある。
もしパフォーマンスに限界を感じたら、ベースとなるパラメータ付きクエリ(あるいはストアドに準ずる保存済みクエリ)を定義しておき、VBAからはQueryDefオブジェクトを介してパラメータを渡す手法への移行を検討せよ。しかし、動的な条件分岐の柔軟性においては、今回紹介したSQL直接書き換え方式に軍配が上がることが多い。

③ デバッグはイミディエイトウィンドウで行え

動的SQLの構築で最も時間を奪われるのは「SQLの構文エラー」だ。コード内に `Debug.Print “Generated SQL: ” & strSQL` を仕込んでいるのはそのためである。
エラーが出たら、イミディエイトウィンドウに出力されたSQLをコピーし、Accessの新規クエリデザイン(SQLビュー)に貼り付けて実行してみる。これだけで、どのクォーテーションが足りないのか、どのフィールド名が間違っているのかが秒速で判明する。

5. おわりに

Access VBAは、レガシーな言語と侮られがちだ。しかし、オブジェクトモデルの挙動、データのライフサイクル、そしてSQLのメカニズムを正しく理解した者が書いたコードは、驚くほど軽快に動き、現場の業務効率を劇的に跳ね上げる。

今回解説した「動的RecordSource制御」は、そのための最も強力な武器の一つだ。
あなたの手元の開発環境でも、スパゲッティなフィルター処理を捨て、洗練された動的SQL構築を今すぐ実装してほしい。圧倒的なレスポンスと、バグの起きない美しさに驚くはずだ。

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