【Access VBA極限講座】クエリの再発行は悪!DAO.RecordsetとFindFirstによる超高速・インメモリ検索エンジンの構築
開発現場でよく見かける悪手がある。ユーザーが検索条件を入力するたびに、SQLクエリを組み立て直して `Me.RecordSource` を書き換え、データベースへラウンドトリップを発生させる設計だ。
データ量が数万件程度であればごまかせても、複数ユーザーが同時接続するネットワーク環境において、この「都度クエリ発行方式」は確実にボトルネックとなる。AccessのJet/ACEエンジンに無駄な負荷をかけ、画面のちらつきやレスポンス低下を引き起こす元凶だ。
真にプロフェッショナルなAccessエンジニアリングとは、「一度メモリ上にロードしたDAOのRecordsetを極限までしゃぶり尽くす」ことである。
今回は、DAOの `FindFirst` メソッドと `NoMatch` 判定を組み合わせ、ミリ秒単位のレスポンスを実現する堅牢なインメモリ検索エンジンの設計手法を伝授する。
—
なぜ `FindFirst` なのか?(アーキテクチャの思想)
フォームの検索機能を作る際、安易に `Me.Filter` と `Me.FilterOn = True` を使うか、あるいはSQLを再発行するアプローチをとりがちだ。しかし、これには明確な限界がある。
1. SQL再発行のコスト: ネットワーク越しのファイルサーバ上にある `.accdb` において、クエリのたびにディスクI/Oやパース処理が発生するのは重罪に近い。
2. `Me.Filter` の罠: 複雑な条件や前方一致・部分一致の組み合わせにおいて、予期せぬ型変換エラーやパフォーマンス劣化を起こしやすい。
対して、フォームにあらかじめ紐付いているレコードソース(またはフォームの裏で保持するクローンレコードセット)をメモリ上で直接操作する手法をとれば、データベースエンジンへの追加負荷はゼロになる。
その中核を担うのが、DAO(Data Access Objects)の `RecordsetClone` と `FindFirst` メソッドだ。
—
堅牢な検索エンジン実装の全体像
実務でそのまま使えるプロダクションコードを提示する。
このコードは、以下の要件を満たすように設計されている。
- SQLインジェクション対策(エスケープ処理): 検索文字列に含まれるシングルクォートなどの特殊文字を適切に処理し、構文エラーを防ぐ。
- ブックマークの同期: `RecordsetClone` で見つけたレコードの位置(Bookmark)を、フォーム本体の `Bookmark` プロパティに一発で転記し、即座にフォーカスを移動させる。
- NoMatchの优雅なハンドリング: 該当データが存在しない場合のユーザーエクスペリエンス(UX)を損なわない。
プロダクションコード例
Option Compare Database
Option Explicit
‘ ==============================================================================
‘ 担当者マスタメンテフォーム:高速インメモリ検索エンジン
‘ ==============================================================================
Private Sub 連携cmdSearch_Click()
On Error GoTo ErrorHandler
Dim rs As DAO.Recordset
Dim strKeyword As String
Dim strCriteria As String
‘ 1. 入力値の取得とサニタイズ(前後の空白除去)
strKeyword = Trim(Me.txtSearchKeyword.Value)
‘ 未入力の場合は処理を抜ける(全件表示に戻す等の制御はお好みで)
If Len(strKeyword) = 0 Then
MsgBox “検索キーワードを入力してください。”, vbInformation, “入力チェック”
Me.txtSearchKeyword.SetFocus
Exit Sub
End If
‘ 2. シングルクォートのエスケープ処理(構文エラー防止の鉄則)
‘ SQLの構文を壊さないよう、’ を ” に置換する
strKeyword = Replace(strKeyword, “‘”, “””)
‘ 3. フォームのレコードセットクローンを取得
‘ ※注意: Form.RecordsetClone は独立したポインタを持つため、フォームの表示位置を乱さずに操作可能
Set rs = Me.RecordsetClone
If rs.RecordCount = 0 Then
MsgBox “検索対象のデータが存在しません。”, vbExclamation, “データなし”
GoTo CleanUp
End If
‘ 4. 検索条件の構築(ここでは社員名[EmployeeName]の部分一致を例とする)
‘ 必要に応じて AND 条件で複数フィールドを結合可能
strCriteria = “EmployeeName LIKE ‘” & strKeyword & “‘”
‘ 5. FindFirstメソッドによる超高速インメモリ検索
rs.FindFirst strCriteria
‘ 6. NoMatchプロパティによる判定
If rs.NoMatch Then
MsgBox “該当するデータは見つかりませんでした。”, vbInformation, “検索結果”
‘ 必要に応じてカレント位置の保持やメッセージ後の処理を記述
Else
‘ 7. 発見したレコードへフォームの表示を即座に同期
Me.Bookmark = rs.Bookmark
‘ ユーザビリティ向上のため、該当コントロールにフォーカスを移動
Me.EmployeeName.SetFocus
End If
CleanUp:
‘ オブジェクト変数の解放(メモリリークの断固阻止)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
—
現場のプロが教える、実装上の極意と注意点
このコードをただコピペするだけでは不十分だ。Access開発で数々の修羅場をくぐり抜けてきたアーキテクトとして、以下の「罠」と「対策」を心に刻んでほしい。
1. `Form.RecordsetClone` の本質を理解せよ
`Me.RecordsetClone` は、現在フォームが保持しているレコードセットの「クローン(複製)」を返す。これはメモリ上にあらかじめ展開されているデータを指しているため、データベースへ再問い合わせを行わない。
ただし、クローンはあくまで「ポインタのコピー」であり、レコードの並び順(Sort)やフィルタ(Filter)の状態はフォーム側の設定を引き継ぐ。そのため、ユーザーがソート順を変えた後でも、その順序を維持したまま検索が可能になるという強力なメリットがある。
2. 複数条件(AND / OR)検索への拡張
実務では、氏名だけでなく「部署ID」や「入社日」など、複合条件で検索したいケースが多々ある。
その場合、`strCriteria` の組み立て部分を動的に変化させればよい。
strCriteria = “1=1″ ‘ プレースホルダー的なベース
If Not IsNull(Me.cmbDepartment) Then
strCriteria = strCriteria & ” AND DepartmentID = ” & Me.cmbDepartment
End If
If Len(Me.txtSearchKeyword.Value) > 0 Then
Dim safeKeyword As String
safeKeyword = Replace(Me.txtSearchKeyword.Value, “‘”, “””)
strCriteria = strCriteria & ” AND (EmployeeName LIKE ‘” & safeKeyword & “‘ OR Kana LIKE ‘” & safeKeyword & “‘)”
End If
rs.FindFirst strCriteria
このように条件を組み立てることで、SQLインジェクションを完全に無効化しつつ、柔軟なインメモリ絞り込みエンジンが完成する。
3. 大量データ(数万件以上)を扱う場合のトレードオフ
メモリ上での `FindFirst` は爆速だが、元となるテーブルやクエリのレコード数が「10万件、20万件」と肥大化している場合、フォームを開く初回のロード (`RecordSource` の評価) 自体が重くなる。
もしデータ量が数万件を超える巨大なテーブルを扱う場合は、インメモリ検索ではなく、最初からサーバー側(ODBC/SQL Server等)で絞り込む「サーバーサイド・ページング」や「パラメータクエリによる都度抽出」に設計を切り替えるべきだ。
「データ量とインフラの特性を見極めて戦略を切り替えること」、これこそが真のアーキテクトの仕事である。
—
総括
今回解説した `DAO.Recordset` と `FindFirst` による検索手法は、スタンドアロン〜小規模部門共有のAccessデータベースにおいて、最もコストパフォーマンスが高く、ユーザーにストレスを与えない黄金律の一つである。
「なんとなく動くコード」ではなく、「なぜこの書き方でなければならないのか」を論理的に説明できるコードベースを構築してほしい。その積み重ねこそが、あなたの作る業務システムの寿命を劇的に延ばすことになると確信している。
