【実務・中級編】QueryDefを活用した「非連結フォーム」の動的フィルタリング:レコードソースを汚さない検索画面の作り方 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:QueryDefで実現する「汚さない検索画面」の設計思想

開発現場でよく見かける光景がある。非連結フォームの検索ボタンが押されるたびに、`Me.RecordSource = “SELECT FROM T_Sales WHERE ” & [組立てた長大なSQL]` と書き換えるコードだ。

一見、動的に動いているから良さそうに見えるが、チーフアーキテクトの視点から言わせてもらえば、これは「マルチユーザー環境における時限爆弾」でしかない。

フォームのレコードソースを直接いじくり回すアプローチは、Jet/ACEデータベースエンジンに不要なクエリのコンパイル負荷を強要し、最悪の場合、bloat(データベースの肥大化)や排他制御の競合を引き起こす。さらに、SQLインジェクションや型の不一致による実行時エラーの温床にもなる。

プロフェッショナルが目指すべきは、「フォームのレコードソースは汚さず、バックグラウンドのQueryDefをスマートに操る」という設計だ。今回は、マルチユーザー環境でも絶対に破綻しない、極限まで洗練された動的フィルタリングの極意を伝授しよう。

—

なぜ「レコードソース直書き」は悪なのか?

フォームの `RecordSource` プロパティに直接SQLを流し込む手法には、構造的な欠陥が3つある。

1. 暗黙のクエリコンパイルコスト
Access(Jet/ACE)は、文字列として渡されたSQLを毎回パースし、実行計画を再構築する。検索のたびにこれが走るため、ネットワーク越しの共有ファイルサーバー環境では致命的な遅延を生む。
2. データベースの肥大化(Bloat)
Access内部では、一時的なSQL文の発行と破棄の繰り返しによってシステムテーブルが断片化し、MDB/ACCDBファイルがみるみる肥大化していく。
3. トランザクションとパラメータの分離不足
入力値をSQL文字列に直接連結(コンカチネーション)すると、シングルクォーテーションのミスによる構文エラーや、セキュリティ上のリスク(SQLインジェクション)を常に抱えることになる。

解決策:一時QueryDefオブジェクトの活用

この問題を一刀両断するのが、「名前付きQueryDef(または一時QueryDef)」の活用だ。
あらかじめパラメータ化されたクエリの枠組みを定義しておき、VBA側からはパラメータ(`Parameters` コレクション)に安全に値をバインドして実行する。これにより、クエリプランのキャッシュが効き、パフォーマンスが劇的に安定する。

—

アーキテクチャ全体像

今回構築する「汚さない検索画面」の仕組みは以下の通りだ。

1. クエリ定義(QueryDef)の固定
抽出のベースとなるSQLを、あらかじめデータベースコンテナ(隠しクエリ、またはコード上の永続QueryDef)に保持する。
2. パラメータの動的注入
VBAから検索条件(顧客名、日付範囲など)をQueryDefのパラメータへ安全に渡す。
3. リストビューやサブフォームへのデータ供給
検索結果を表示するグリッド(非連結、またはパラメータクエリをソースフとしたサブフォーム)にデータを流し込む。今回は最も堅牢な「パラメータクエリをバインドしたサブフォームに対し、親側からQueryDefのパラメータを制御する手法」を採用する。

—

プロダクションコード:堅牢な検索ロジックの実装

それでは、実際の現場でそのまま使えるコードを提示しよう。
前提として、検索条件を入力する非連結フォーム(`F_Search`)と、結果を表示するサブフォーム(`F_Search_Sub`)が存在し、サブフォームのレコードソースには以下の名前付きクエリ(例: `qry_Sales_Search`)が設定されているとする。

1. データベース側に用意するベースクエリ (`qry_Sales_Search`)

PARAMETERS
[prmCustomerName] Text ( 255 ),
[prmDateFrom] DateTime,
[prmDateTo] DateTime;
SELECT
SalesID,
CustomerName,
SalesDate,
Amount
FROM
T_Sales
WHERE
(CustomerName LIKE [prmCustomerName] OR [prmCustomerName] IS NULL)
AND (SalesDate >= [prmDateFrom] OR [prmDateFrom] IS NULL)
AND (SalesDate <= [prmDateTo] OR [prmDateTo] IS NULL); このクエリの優れた点は、パラメータがNull(未入力)の場合のフォールバックがSQL側で完結していることだ。VBA側で複雑な「AND / OR」の文字列組み立てを行う必要が一切なくなる。

2. VBA側の検索実行コード(非連結フォーム側)

検索ボタンのクリックイベントに記述するコードである。エラーハンドリングを徹底し、トランザクションやオブジェクトのライフサイクルを厳密に管理する。

Option Compare Database
Option Explicit

Private Sub btnSearch_Click()
On Error GoTo ErrorHandler

Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim subFrm As Form

‘ データベースオブジェクトの取得
Set db = CurrentDb

‘ 1. 永続QueryDefオブジェクトを参照として取得
‘ ※一時クエリではなく、あらかじめ保存したクエリ定義を安全に呼び出す
Set qdf = db.QueryDefs(“qry_Sales_Search”)

‘ 2. 画面の入力コントロールから安全に値を取得し、パラメータにバインド
‘ Nz関数を使い、未入力(Null)の場合はVarian型のNullを渡すことでSQL側のIS NULL判定を活かす
qdf!prmCustomerName = IIf(IsNull(Me.txtCustomerName), Null, “” & Me.txtCustomerName & “”)
qdf!prmDateFrom = IIf(IsNull(Me.txtDateFrom), Null, Me.txtDateFrom)
qdf!prmDateTo = IIf(IsNull(Me.txtDateTo), Null, Me.txtDateTo)

‘ 3. サブフォームのレコードソースを再クエリ(Requery)して変更を反映
Set subFrm = Me.fsubResults.Form
subFrm.Requery

‘ 4. ステータス表示の更新(UXの向上)
Me.lblStatus.Caption = “検索完了: ” & subFrm.Recordset.RecordCount & ” 件ヒット”

CleanUp:
‘ オブジェクト変数の明示的な解放(メモリリークの防止)
Set qdf = Nothing
Set db = Nothing
Exit Sub

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

—

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

① 文字列連結によるSQL構築の完全排除

初心者によくある `SQL = “SELECT FROM T WHERE Name = ‘” & Me.txt & “‘”` というコードは、シングルクォーテーションの混入によるSyntax Error(構文エラー)やSQLインジェクションの温床になる。
QueryDefの `Parameters` コレクションを経由することで、Accessエンジンが型を厳密に解釈し、安全かつ高速に値がバインドされる。

② `Nz` とワイルドカードの巧妙な組み合わせ

あいまい検索(LIKE演算子)を行う際、パラメータに “ を付与するのはVBA側で行うのが定石だ。

qdf!prmCustomerName = IIf(IsNull(Me.txtCustomerName), Null, “” & Me.txtCustomerName & “”)

このように記述することで、ユーザーが何も入力しなかった場合はパラメータ自体が `Null` となり、SQL側の `[prmCustomerName] IS NULL` 条件がヒットして「全件抽出」に切り替わる。条件分岐の `If` 文を何重にも書く必要がなくなるため、保守性が劇的に向上する。

③ オブジェクトのライフサイクル管理とメモリリーク対策

DAOの `Database` や `QueryDef` を扱う際、`Set qdf = Nothing` を怠ると、VBAのランタイムがメモリ上にオブジェクトを保持し続け、長時間の稼働でAccessが不安定になる(リソースリーク)。
`On Error GoTo` を用いたクリーンアップブロック(`CleanUp:`)を必ず用意し、例外発生時であっても確実に参照を切るコードスタイルをチーム標準とせよ。

—

結びにかえて

業務システムにおいて、「動くこと」と「耐用性があること」は全く次元が違う。
今回紹介したQueryDefを活用した動的フィルタリング設計は、どれだけデータが増えようとも、どれだけ同時接続ユーザーが増えようとも、常に安定したパフォーマンスと堅牢性を提供し続ける。

場当たり的な `RecordSource` の書き換えは今すぐ封印し、構造化された美しいクエリ設計へシフトしてほしい。あなたの書くコードが、そのシステムの寿命を決定するのだから。

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