【実務・中級編】DAO.Recordsetの「NoMatch」判定と「FindFirst」を組み合わせた、高速なデータ検索エンジンの構築 – Access VBA解析バイブル

スポンサーリンク

DAO.RecordsetとFindFirstの極意:Accessの底力を引き出す高速検索エンジンの設計

開発現場でよく見かける光景がある。テーブルから特定のレコードを探すために、わざわざ遅い `DLookup` をループさせたり、重いSQLを発行して `CurrentDb.OpenRecordset` を毎回叩いたりするコードだ。

「Accessは遅い」と嘆く前に言いたい。それはAccessが遅いのではなく、あなたのデータの引き出し方が間違っているのだ。

Accessの真髄は、背後に潜むJet/ACEデータベースエンジンと、DAO(Data Access Objects)の圧倒的な親和性にある。今回は、インデックスをフル活用した `DAO.Recordset` の `FindFirst` メソッドと、`NoMatch` プロパティを組み合わせ、実務の現場で秒速のレスポンスをもたらす「堅牢なデータ検索エンジン」の構築法を伝授しよう。

なぜ `DLookup` や全件走査ではダメなのか?

実務で数万〜数十万件のレコードを扱うシステムにおいて、パフォーマンスは正義である。

1. `DLookup` の限界
`DLookup` は手軽だが、呼び出すたびに内部でクエリの解析と実行が行われる。これをループ内で回すと、データベースエンジンに多大な負荷がかかり、画面がフリーズしたような遅延を引き起こす。
2. 不適切な `Seek` メソッドの罠
「インデックスを使うなら `Seek` の方が速い」という教科書的な意見もある。確かに `Seek` は最速だが、テーブルタイプ(Table-type)のレコードセットでしか使えず、かつODBCリンクテーブルでは完全に乗算不能という致命的な足枷がある。ローカル完結の小規模な用途ならともかく、将来的なスケーラビリティを考慮すれば、あらゆるRecordsetタイプ(Dynaset / Snapshot)で安定して使える `FindFirst` こが実務の主力となるべきだ。

`FindFirst` を真の高速エンジンにする条件

`FindFirst` は、適切にインデックスが設計されていれば、SQLの `WHERE` 句に匹敵する速度でレコードをピンポイントで捉える。ここで重要なのは、検索対象のフィールドに必ずAccess側でインデックス(インデックス:はい、重複なし/重複あり)を設定しておくことだ。ここが抜けていると、いくらコードを最適化してもテーブルの全件走査(フルスキャン)が発生する。

堅牢な設計の要:`NoMatch` 判定の作法

`FindFirst` を使用する際、最もやりがちなバグが「見つからなかった場合(NoMatch)」のハンドリング漏れだ。

`FindFirst` は、条件に一致するレコードが見つからない場合、エラーを発生させない。その代わり、Recordsetオブジェクトの `NoMatch` プロパティを `True` に書き換える
この仕様を見落とし、「見つかった前提」で後続のフィールド参照処理(例: `!ColumnName`)を書くと、容赦なく 実行時エラー 3021(「カレント レコードがありません。」) が発生する。

プロのエンジニアであれば、検索処理は常に「見つからないかもしれない」という前提(Exception Path)をファーストクラス市民としてコードに組み込むべきだ。

実践:プロダクションコード例

それでは、実務の現場でそのままコピー&ペーストして使える、堅牢性と高速性を兼ね備えた検索関実装コードを提示する。

このコードは、社員マクロテーブルから指定された「社員番号」を検索し、存在すればその情報を取得、存在しなければ適切にハンドリングして新規登録モード等へ分岐させるシナリオを想定している。

Option Compare Database
Option Explicit

‘ =================================================================================
‘ 処理名 : FindEmployeeFast
‘ 概要 : DAO.FindFirstとNoMatchを活用した高速社員検索エンジン
‘ 引数 : strEmployeeNo – 検索する社員番号 (String)
‘ 戻り値 : Boolean (見つかった場合はTrue、見つからなかった場合はFalse)
‘ 備考 : 呼び出し元でCurrentDbのライフサイクルを意識し、トランザクションや
‘ エラーハンドリングを適切に行うこと。
‘ =================================================================================
Public Function FindEmployeeFast(ByVal strEmployeeNo As String) As Boolean
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strCriteria As String

‘ 戻り値の初期化
FindEmployeeFast = False

‘ 入力値のバリデーション(ガード cláusule)
If Trim(strEmployeeNo & “”) = “” Then
MsgBox “検索する社員番号が指定されていません。”, vbExclamation, “入力エラー”
Exit Function
End Function

On Error GoTo ErrorHandler

‘ データベースインスタンスの取得
Set db = CurrentDb()

‘ Dynaset型レコードセットのオープン(※検索対象フィールドにはテーブル側でインデックス設定必須)
‘ パフォーマンスとメモリ効率を考慮し、必要なレコードのみを絞り込むSQLをベースにする
Set rs = db.OpenRecordset(“SELECT FROM M_Employee”, dbOpenDynaset)

‘ 検索条件の構築(文字列の場合はシングルクォーテーションで囲む。数値なら不要)
strCriteria = “EmployeeNo = ‘” & Replace(strEmployeeNo, “‘”, “””) & “‘”

‘ 高速検索の実行
rs.FindFirst strCriteria

‘ 【極意】NoMatchプロパティによる厳密な存在判定
If rs.NoMatch Then
‘ — 該当データが存在しない場合の処理 —
Debug.Print “検索結果: 該当する社員番号 [” & strEmployeeNo & “] は見つかりませんでした。”
‘ ここに新規登録へ誘導するロジックなどを記述
GoTo Cleanup
End If

‘ — 該当データが存在した場合の処理 —
‘ カレントレコードが正しくヒットしているため、安全にフィールドへアクセス可能
MsgBox “社員名: ” & rs!EmployeeName & ” が見つえました。”, vbInformation, “検索成功”

‘ 処理成功のフラグを立てる
FindEmployeeFast = True

Cleanup:
‘ オブジェクトの明示的な解放(メモリリークの完全阻止)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Function

ErrorHandler:
‘ 予期せぬランタイムエラーのキャッチ
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error No: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, vbCritical, “システムエラー”
Resume Cleanup
End Function

アーキテクトからの設計上の忠告

上記のコードには、プロフェッショナルとして譲れない設計思想がいくつか埋め込まれている。これを解説して締めくくりとする。

1. SQLインジェクション対策への配慮
仮に検索条件を画面のテキストボックスからダイレクトに受ける場合、`Replace(strEmployeeNo, “‘”, “””)` のような最低限のエスケープ処理(あるいはパラメータクエリの活用)を忘れてはならない。VBAの文字列結合によるSQL構築は、常にインジェクションや構文エラーのリスクを孕んでいることを忘れるな。
2. リソースの確実な解放(Memory Leak Prevention)
Access VBAにおいて、`DAO.Recordset` や `DAO.Database` のオブジェクト変数をそのまま放置すると、ガベージコレクションのタイミングやAccess終了時までメモリ上に残存し、リソースリークやファイル破損の原因となる。必ず `Cleanup` ラベルを用意し、`rs.Close` と `Set xx = Nothing` をセットで通すフローを強制的につくること。
3. エラーハンドリングの局所化
「エラーが出たらとりあえず `Resume Next` でスルーする」という悪習は、バグを隠蔽する最悪のアンチパターンだ。`NoMatch` はエラーではなく「データが存在しないという正常系のビジネスロジックの結果」である。これを混同せず、システムエラー(Err.Number)とビジネス上の不一致(NoMatch)を完全に分離してコードを書くこと。

この設計思想を取り入れれば、あなたの作るAccessアプリケーションは見違えるほど軽快に、そしてビクともしない堅牢性を手に入れるはずだ。現場のエンジニアよ、泥臭いコードから脱却し、構造化された美しいVBAを実装してほしい。

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