Access VBAを掌握する極限の知見:DAO.RecordsetとFindFirstによる高速検索エンジンの構築
開発現場でよく見かける光景がある。ユーザーが画面上のコンボボックスで値を選び、「検索」ボタンを押すたびに、Accessが重いSQLクエリを発行し、フォームのレコードソースを丸ごと書き換える。
――待ってほしい。本当にそのアプローチは正しいのか?
数万件程度のローカルデータであれば、毎回Jet/ACEデータベースエンジンにSQLを解釈させ、ディスクIOを発生させるのは極めて非効率な設計だ。ネットワーク越し(クライアント・サーバー型)のAccessバックエンドであれば、なおさらトラフィックの無駄遣いである。
真にプロフェッショナルなAccess開発者が選ぶのは、「一度メモリ上に展開したDAO.Recordsetをインデックスで高速に舐め尽くす」というアプローチだ。
今回は、DAOの `FindFirst` メソッドと `NoMatch` プロパティを極限までチューニングし、バグの起きない堅牢なデータ検索エンジンを構築する手法を伝授する。
—
なぜ `FindFirst` なのか?(SQL発行との決別)
データベース検索において、SQL (`SELECT FROM … WHERE …`) は万能ではない。フォームのレコードソース(RecordSource)を動的に書き換える手法には、以下の致命的なデメリットがある。
1. オーバーヘッドの塊: クエリのパース、最適化プランの生成、レコードセットの再構築コストが毎回発生する。
2. UIのちらつき(Flicker): レコードソースの再割り当てに伴い、フォームの描画更新が走り、ユーザー体験が著しく低下する。
3. ブックマークの喪失: レコードソースを切り替えると、現在位置や選択状態がリセットされる。
これに対し、フォームの背後にあるレコードセットのクローン(`RecordsetClone`)に対して `FindFirst` を実行する手法は、メモリ上でポインタを動かすだけである。データベースエンジンに対する無駄な負荷を極限まで削ぎ落とし、ミリ秒単位の爆速レスポンスを実現できる。
—
堅牢な検索エンジン設計の要諦
実務の現場でこの手法を実装する際、素人が書いたコードは必ず「実行時エラー」や「予期せぬ挙動」の罠にハマる。以下の3点を必ず死守せよ。
1. `NoMatch` の判定は「直後」に行う
`FindFirst` がヒットしなかった場合、エラーは発生しない。その代わり、レコードセットの `NoMatch` プロパティが `True` になる。この判定を怠ったり、他の処理を挟んでから判定したりすると、カレントレコードのポインタ位置が不正になり、致命的なバグを生む。
2. ブックマークの同期
メモリ上のクローン(`RecordsetClone`)で見つけたレコードの位置(Bookmark)を、実際のフォーム(`Me.Bookmark`)に渡すことで、画面の表示位置が一瞬でそのレコードにジャンプする。
3. トランスポートセーフティ(適切なエラーハンドリングとクリーンアップ)
DAOオブジェクトはメモリを消費する。特に `RecordsetClone` はフォームが開いている間は存在するが、明示的な変数管理やエラー時のジャンプ先(`ErrorHandler`)を怠ると、メモリリークやロックの温床となる。
—
プロダクションコード例:実務で即戦力となる検索エンジン
以下のコードは、顧客IDなどのキーワードを元に、フォーム上のレコードを瞬時に検索・フォーカスする実用的なプロシージャである。そのままプロジェクトに組み込んでほしい。
‘ ==============================================================================
‘ 担当者・モジュール名: 検索エンジン制御モジュール (Form_frmCustomers)
‘ 概要: DAO.RecordsetClone と FindFirst を用いた高速データ検索処理
‘ ==============================================================================
Public Sub SearchCustomerRecord(ByVal targetID As Long)
Dim rs As DAO.Recordset
Dim searchCriteria As String
On Error GoTo ErrorHandler
‘ 1. フォームのレコードセットのクローンを取得
‘ (※フォーム自体のカレント位置を汚さずにメモリ上で操作するため)
Set rs = Me.RecordsetClone
‘ 2. 検索条件の構築 (DAOの構文規則に従う。数値型の場合はそのまま連結)
‘ ※文字列型の場合はシングルクォーテーションで囲むこと: “CustomerID = ‘” & 検索値 & “‘”
searchCriteria = “CustomerID = ” & targetID
‘ 3. FindFirst メソッドによる高速メモリ内検索
rs.FindFirst searchCriteria
‘ 4. NoMatch プロパティによるヒット判定(極めて厳格に行う)
If rs.NoMatch Then
‘ 該当データなしのハンドリング
MsgBox “指定された顧客ID (” & targetID & “) は見つかりませんでした。”, _
vbExclamation, “検索結果”
GoTo Cleanup
End If
‘ 5. フォームの表示位置(Bookmark)を検索結果に同期
Me.Bookmark = rs.Bookmark
Cleanup:
‘ 6. オブジェクト変数の解放(メモリリークの完全防止)
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
—
現場で役立つチューニングの極意
この検索エンジンをさらに極限まで高速化するための「裏技」を授けよう。
1. インデックスの貼付(必須):
`FindFirst` は強力だが、検索対象となるテーブル側のフィールド(上記の例では `CustomerID`)にインデックスが設定されていない場合、フルスキャン(全件走査)が発生し、結局遅くなる。バックエンドのテーブル設計において、検索キーとなるフィールドには必ずインデックスを付与せよ。
2. 文字列検索のワイルドカード:
部分一致検索(例:「株式会社」を含む社名検索)を行いたい場合は、条件式を以下のように構築する。
searchCriteria = “CompanyName Like ‘” & Me.txtKeyword.Value & “‘”
rs.FindFirst searchCriteria
前方一致であれば “ を末尾だけにすることで、インデックスが効くケースもあるため、パフォーマンス要件に応じて使い分けること。
3. 複数条件の結合:
`And` 演算子を用いて複合条件での検索も可能だ。
searchCriteria = “Status = 1 And Prefecture = ‘東京都'”
rs.FindFirst searchCriteria
—
アーキテクトからの総括
プログラミングにおいて、「動けばいい」という妥協は技術的負債の第一歩である。
今回紹介した `DAO.Recordset` と `FindFirst` によるアプローチは、Accessが持つポテンシャルを極限まで引き出すための王道にして最速のテクニックだ。
無駄なSQL発行を断ち切り、メモリ上で完結するスマートなアーキテクチャを実装することで、あなたの作るAccessアプリは見違えるほど軽快で、プロフェッショナルなツールへと生まれ変わるだろう。
現場のエンジニアよ、今すぐその非効率なクエリ書き換えコードをリプレースせよ。
