【実務・中級編】フォームのRecordsetCloneとBookmarkを同期させ、非連結フォームで検索結果を表示するテクニック – Access VBA解析バイブル

スポンサーリンク

【Access VBA極限の知見】RecordsetCloneとBookmarkの神髄:非連結検索フォームを実務レベルで完全掌握する

こんにちは。チーフアーキテクトの私だ。
日々、現場からの「Accessが重い」「検索フォームの挙動がおかしい」「マルチユーザー環境でエラー吐いて止まる」という悲鳴を聞くのに飽き飽きしていないか?

Access開発において、「検索条件を指定してヒットしたレコードへスムーズにジャンプさせたい」という要求は、もはや避けて通れない基本要件だ。しかし、この実装で多くの初学者、いや、中途半端なベテランすらもが犯す致命的なミスがある。

それが、「検索のたびに `Me.RecordSource` を書き換える」 という愚行だ。

画面がチラつき、マルチユーザー環境では排他制御のロック競合を引き起こし、何よりコードの保守性をドブに捨てるようなそのアプローチは、今日この瞬間から封印してほしい。

今回は、Accessオブジェクトモデルの深淵を突く「RecordsetClone」と「Bookmark」の同期テクニックを授けよう。連結フォームの利便性を1ミリも損なわず、爆速かつ堅牢な検索・表示UIを構築するプロの技法を解説する。

なぜ `RecordSource` の動的書き換えは「悪」なのか?

検索ボタンを押されるたびに、以下のようなコードを書いている者はいないか?

‘ 【絶対に真似してはならないアンチパターン】
Private Sub btnSearch_v1_Click()
‘ 検索のたびにSQLを再発行し、フォームの基盤をごっそり入れ替える
Me.RecordSource = “SELECT FROM T_Customer WHERE CustomerName LIKE ‘” & Me.txtKeyword.Text & “‘”
Me.Requery
End Sub

一見動くように見えるこのコードは、実務の現場では地雷原だ。
なぜなら:
1. パフォーマンスの殺戮: フォームが保持するすべてのレコードキャッシュが破棄され、Jet/ACEエンジンに無駄なクエリコンパイルとI/Oを強いる。
2. カーソル位置の喪失: レコードソースを書き換えると、カレントレコードは強制的に先頭へジャンプし、ユーザーがどこを見ていたのかが分からなくなる。
3. トランザクションとロックの肥大化: フォーム自体のデータソースを頻繁に変更することは、マルチユーザー環境において意図しないレコードロックの範囲拡大を招く。

究極のアーキテクチャ:非連結コントロール+RecordsetClone

プロが目指すべき設計思想はこうだ。

  • フォームの `RecordSource` は固定し、フォーム起動時に一度だけバインドする。
  • 検索は「テキストボックス(非連結)」等で受け付ける。
  • フォームが裏で持っているクエリの結果セット(`RecordsetClone`)に対してヒット位置を割り出し、`Bookmark`(ブックマーク)を使って一瞬でカーソルをジャンプさせる。

このアプローチにより、データベースへの無駄な負荷を消し去り、UIの反応速度を極限まで高めることが可能になる。

実装の全体像とオブジェクトのライフサイクル

Accessのフォームオブジェクトには、裏でメモリ上に展開されているデータへの参照である `RecordsetClone` という強力なプロパティが存在する。

フォーム本体のレコードポインタ(カレントレコード)と、`RecordsetClone` のポインタは完全に独立している。つまり、ユーザーに画面のちらつきを見せることなく、裏側のクローンレコードセット側で自由自在に検索(`FindFirst` など)を行い、合致したレコードの「住所」である `Bookmark` を取得して、それをフォーム本隊に教えてやればいいのだ。

【実践】コピペで動くプロダクションコード

以下のコードは、顧客管理テーブル(`T_Customer`)を想定した、実務でそのまま使える堅牢な検索・同期ロジックだ。エラーハンドリング(防衛的プログラミング)も完全に網羅してある。

前提条件

  • フォームのレコードソース(`RecordSource`)には、あらかじめ `T_Customer`(または十分なパフォーマンスを持つクエリ)が設定されていること。
  • 検索キーワードを入力する非連結のテキストボックス名が `txtSearchKeyword` であること。
  • ジャンプ対象の主キー(ID)フィールド名が `CustomerID`(数値型)であること。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 担当者コード: 検索キーワードに基づき、RecordsetCloneとBookmarkを用いて
‘ フォームの表示位置を該当レコードへ高速同期させるプロシージャ
‘ ==============================================================================
Private Sub btnExecuteSearch_Click()
On Error GoTo ErrorHandler

Dim rsClone As DAO.Recordset
Dim strKeyword As String
Dim strCriteria As String

‘ 1. 入力値の検証とサニタイジング(ワイルドカードのエスケープ等)
If Trim(Me.txtSearchKeyword.Value & “”) = “” Then
MsgBox “検索キーワードを入力してください。”, vbExclamation, “入力チェック”
Me.txtSearchKeyword.SetFocus
Exit Sub
End If

‘ SQLインジェクションや特殊文字対策としてシングルクォーテーションをエスケープ
strKeyword = Replace(Me.txtSearchKeyword.Value, “‘”, “””)

‘ 2. フォームのRecordsetCloneを取得
‘ ※注意: Closeメソッドを呼ぶ必要はない。フォームのライフサイクルに依存する。
Set rsClone = Me.RecordsetClone

‘ 3. レコードが存在するかどうかのチェック
If rsClone.RecordCount = 0 Then
MsgBox “検索対象となるデータが存在しません。”, vbInformation, “通知”
Set rsClone = Nothing
Exit Sub
End If

‘ 4. 検索条件の構築 (例: 顧客名または担当者名部分一致)
‘ ※必要に応じてフィールド名を実際の設計に合わせて変更すること
strCriteria = “CustomerName LIKE ‘” & strKeyword & “‘ OR ContactPerson LIKE ‘” & strKeyword & “‘”

‘ 5. クローンレコードセットに対してFindFirstを実行
rsClone.FindFirst strCriteria

‘ 6. 検索結果の判定と同期処理
If rsClone.NoMatch Then
‘ 該当なしの場合
MsgBox “一致するレコードが見つかりませんでした。”, vbInformation, “検索結果”
Else
‘ 7. 【核心】見つかったレコードのBookmarkをフォーム本体に渡す
Me.Bookmark = rsClone.Bookmark

‘ 視覚的なフィードバック(必要に応じてフォーカス移動)
‘ Me.CustomerName.SetFocus
End If

CleanUp:
‘ 8. オブジェクト変数の解放(メモリリークの防止)
Set rsClone = Nothing
Exit Sub

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

現場で絶対に知っておくべき「技術的罠」と回避策

このコードを実装するにあたり、チーフアーキテクトとしていくつか現場の罠を警告しておこう。

1. `RecordsetClone` の型は必ず `DAO.Recordset` にせよ

ADODBではなく、DAO であることに注意せよ。Access VBAにおいてフォームが内部保持するレコードセットは常にDAOのレイヤーで動いている。`Dim rs As Recordset` と曖昧に宣言すると、参照設定の優先順位によってADOと誤認され、`Bookmark` 代入時に「型が一致しません」という不可解なエラーで夜を明かすことになる。必ず `DAO.Recordset` と明示せよ。

2. マルチユーザー環境における Bookmark の無効化

Aさんがレコードを削除した直後、Bさんが古い `Bookmark` を保持したままジャンプしようとすると、Accessは容赦なく「実行時エラー ‘3158’: ブックマークが無効です」をスローする。
これを防ぐためには、`FindFirst` の前に `rsClone.Requery` を挟むか、あるいはエラーハンドラ内で `Err.Number = 3158` をキャッチし、「他のユーザーによってデータが変更または削除されました。検索をやり直してください」と優しくユーザーに導くフェイルセーフな設計が不可欠だ。

3. パフォーマンスの境界線

`RecordsetClone` + `FindFirst` は、ローカルDB(数万〜数十万レコード)であれば秒速で動作する。しかし、バックエンドがSQL ServerなどのODBCリンクテーブルであり、数百万レコードを超える規模である場合、`FindFirst` はクライアント側での逐次スキャン(あるいは非効率なクエリ発行)を引き起こすことがある。
その場合は、インデックスが適切に張られているかを確認し、必要であれば `ServerFilter` や適切なパススルー・クエリの併用を検討すべきだ。だが、一般的な業務アプリの検索UIであれば、今回紹介した手法で十分すぎるほどのパフォーマンスが出せるはずだ。

最後に:コードの品格を保て

Accessは「誰でも簡単に作れる」がゆえに、スパゲッティコードの温床になりやすい。
しかし、今回解説した `RecordsetClone` と `Bookmark` の同期メカニズムを正しく理解し実装できれば、あなたの作るAccessアプリは、市販のパッケージソフトに匹敵する軽快で洗練されたUXを手に入れる。

「動けばいいや」の精神を捨て、細部までこだわり抜いたアーキテクチャを構築してほしい。君の健闘を祈る。

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