Access VBAを掌握する極限の知見:DAO.Recordsetの「Clone」と「Bookmark」がもたらす、非連結フォーム高速検索UIの極意
レガシーシステムの最前線に立ち続ける我々にとって、Microsoft Accessは時として諸刃の剣となる。
特に、数万〜数十万件のレコードを抱えるテーブルに対して、ユーザーがフォーム上のテキストボックスに文字を入力するたびに `Me.RecordSource = “SELECT … WHERE …”` を実行するような愚行を犯していないだろうか?
そんなアプローチは、Jet/ACEエンジンのクエリ評価コストとネットワーク(あるいはローカルI/O)のオーバーヘッドを直撃し、アプリケーションを使い物にならないガラクタへと変貌させる。
真のプロフェッショナルは、Accessのオブジェクトモデルの裏側にあるライフサイクルを熟知している。
今回は、`RecordsetClone` と `Bookmark` を極限まで駆使し、フォームのレコードソースを一切汚さずに、ミリ秒単位のレスポンスを実現する高速検索UIの構築手法を授けよう。
—
1. なぜ「直接のレコードソース書き換え」は悪なのか?
フォームの `RecordSource` を動的に変更するアプローチには、致命的な構造的欠陥がある。
1. クエリの再コンパイルとI/Oの発生: SQL文を変更するたびに、データベースエンジンは実行計画の再構築とデータフェッチを強制される。
2. カレントレコードの喪失とUIのちらつき: レコードソースの再設定は、フォームの再描画(Repaint)を伴い、UX(ユーザーエクスペリエンス)を著しく低下させる。
3. トランザクションとロックの競合リスク: 不要なクエリ発行は、マルチユーザー環境においてデッドロックの温床となる。
解法:メモリ上で完結させる「影のレコードセット(Clone)」
Accessのフォーム(`Form` オブジェクト)は、背後に必ず単一のDAO(またはADO)レコードセットのインスタンスを保持している。これが `RecordsetClone` だ。
`RecordsetClone` は、フォームが現在開いているデータソースのメモリ上のクローン(複製)であり、フォームの表示状態(カレントレコードやフィルタ)に影響を与えることなく、独立して操作できる。
このクローンに対して検索を行い、合致したレコードの `Bookmark`(ブックマーク) をフォーム本体の `Bookmark` プロパティに転写する。
たったこれだけの設計思想の転換が、システムを「爆速」へと昇華させる。
—
2. 実装アーキテクチャ:高速検索モジュールの全貌
実際の業務システムで即座に使える、洗練された実装コードを提示する。
以下のコードは、検索用テキストボックスの `Change` イベント、あるいは検索ボタンのクリックイベントから呼び出すことを想定した汎用プロシージャである。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 汎用高速検索・位置合わせプロシージャ
‘
‘ [引数]
‘ frm : 対象のフォーム (Me)
‘ searchField : 検索対象のフィールド名 (例: “CustomerName”)
‘ searchValue : 検索する文字列 (例: “株式会社”)
‘ =========================================================================
Public Sub FastSearchByClone(frm As Form, ByVal searchField As String, ByVal searchValue As String)
Dim rsClone As DAO.Recordset
On Error GoTo ErrorHandler
‘ フォームのRecordsetCloneを取得
‘ ※ 注意: 新規レコード行(Null)にいる場合の対策として、あらかじめ取得しておく
Set rsClone = frm.RecordsetClone
‘ クローンが存在しない、またはレコードがない場合は即座に抜ける
If rsClone.RecordCount = 0 Then
Set rsClone = Nothing
Exit Sub
End If
‘ 検索文字列が空の場合は、先頭に戻して終了(必要に応じてカスタマイズ)
If Trim(searchValue & “”) = “” Then
frm.Bookmark = rsClone.Bookmark ‘ 先頭またはデフォルトへ
GoTo CleanUp
End If
‘ DAOのFindFirstメソッドによる高速インデックス検索
‘ ※ searchField にインデックスが貼られていることが前提条件(極限のパフォーマンス追求)
Dim criteria As String
‘ 文字列型のエスケープ処理(シングルクォーテーション対策)
‘ ※ 本番環境ではSQLインジェクションや構文エラーを防ぐため厳密なエスケープを推奨
criteria = “[” & searchField & “] LIKE ‘” & Replace(searchValue, “‘”, “””) & “‘”
rsClone.FindFirst criteria
‘ 該当レコードが見つかった場合のみ、フォームのBookmarkを同期
If Not rsClone.NoMatch Then
‘ ここがキモ:フォームのBookmarkをクローンのBookmarkに一致させることで、
‘ レコードソースを再クエリすることなく、一瞬で該当行へジャンプする。
frm.Bookmark = rsClone.Bookmark
Else
‘ 見つからなかった場合の処理(必要に応じてメッセージやステータスバー更新)
‘ 無音でスルーするのが実務では好まれることが多い
End If
CleanUp:
‘ 【重要】オブジェクトの明示的解放
‘ VBAのDAOオブジェクトはスコープ抜けで即座に解放されないケースがあるため、
‘ シニアエンジニアの作法として必ずSet Nothingを行うこと。
If Not rsClone Is Nothing Then
rsClone.Close
Set rsClone = Nothing
End If
Exit Sub
ErrorHandler:
MsgBox “検索処理中に予期せぬエラーが発生しました。” & vbCrLf & _
“Error ” & Err.Number & “: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
—
3. チーフアーキテクトが教える、現場で活きる「3つの極意」
上記の基本コードに加え、実際の巨大なエンタープライズ環境で生き抜くための実践的知見を授けよう。
① インデックス設計の徹底(ハードウェアの限界を超える)
`FindFirst` メソッドは強力だが、検索対象フィールド(`searchField`)にインデックス(Index)が貼られていない場合、フルスキャン(逐次検索)となり、データ量に比例してパフォーマンスが劣化する。
Access/Jetエンジンの特性上、頻繁に部分一致・前方一致検索を行うカラムには、必ずテーブルデザイン側でインデックスを付与しておけ。これだけで体感速度は100倍以上変わる。
② イベントの連鎖と「Change」イベントの罠
ユーザーのタイピングに合わせてリアルタイム検索(いわゆるインクリメンタルサーチ)を実装する場合、テキストボックスの `Change` イベントを使うことになる。
ここで注意すべきは、`Me.Bookmark = rsClone.Bookmark` の実行が、フォームのフォーカスや再描画を引き起こし、意図しないイベントのループやカーソル飛びを引き起こすリスクがある点だ。
実務においては、`Keyup` イベントでキーコード(Enterなど)をフックするか、タイマーイベント(TimerInterval)を併用したディレイ処理を実装するのが、プロフェッショナルの選択となる。
③ メモリリークを絶対に防ぐ作法
Access VBAにおけるDAOの扱いで最も恐ろしいのは、ガベージコレクションの気まぐれによるメモリリークと、それに伴うファイルサイズ肥大化(Bloat)だ。
`Form.RecordsetClone` から取得したオブジェクトであっても、使い終わったら明示的に `.Close` し、変数に `Nothing` を代入する。この規律をチーム全体で徹底させることが、10年稼働するレガシーシステムを支える唯一の防壁となる。
—
4. 結びにかえて
「Accessだから遅い」のではない。「Accessの正しい扱い方を知らない」から遅いのだ。
今回紹介した `RecordsetClone` と `Bookmark` を組み合わせたアプローチは、データベースの物理I/Oを最小限に抑えつつ、リッチなデスクトップアプリケーションと同等のUIフィードバックを実現する究極のテクニックである。
凡百のコードを捨て、オブジェクトのライフサイクルとメモリ構造を支配するコードを書け。それこそが、真のAccessマイスターの姿である。
