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

スポンサーリンク

Access VBAを掌握する極限の知見:DAO.Recordset「FindFirst」と「NoMatch」による超高速・高信頼データ検索エンジンの構築

数百万件のレコードを抱えるレガシーなAccessデータベースにおいて、「DLookupやDCountをループ内で回す」「毎回クエリを動的に生成してDAOを開閉する」といった実装は、システムを死に至らしめるアンチパターンである。ネットワークやメモリの帯域が限られた環境、あるいはローカルのJet/ACEエンジンを極限まで酷使する現場において、我々シニアエンジニアに求められるのは、オブジェクトのライフサイクルを完全に掌握し、ミリ秒単位で処理時間を削ぎ落とした「真のデータ検索エンジン」の構築に他ならない。

今回は、DAO(Data Access Objects)のインデックスを完全活用した `FindFirst` メソッドと、検索失敗時の判定プロパティである `NoMatch` を組み合わせ、過酷な現場に耐えうる堅牢かつ高速な検索モジュールの実装手法を、アーキテクトの視点から解き明かす。

—

1. なぜ「FindFirst + NoMatch」なのか:ADOとの決定的な違い

まず、オブジェクトモデルの選定から話を始めよう。Accessの内部データストア(ACE/Jet)を操作する場合、ADO(ActiveX Data Objects)の `Recordset.Find` よりも、ネイティブなDAOの `Recordset.FindFirst` を選択すべきである。

  • Jet/ACEエンジンとの親和性: DAOはAccessのデータベースエンジンと直接対話するため、余計なプロバイダ層を介さず、インデックスをダイレクトにヒットさせる。
  • NoMatchプロパティの明快さ: 検索がヒットしなかった場合、エラー(トラップ可能な実行時エラー)を発生させるのではなく、`NoMatch` プロパティが `True` を返す。これにより、無駄な `On Error GoTo` のオーバーヘッドを排除し、極めてクリーンで高速な分岐処理が可能になる。

—

2. 実装の極意:メモリ管理とインデックスの強制

実務で使える検索エンジンを構築するには、単にメソッドを呼び出すだけでは不十分だ。以下の3点をコードレベルで担保しなければならない。

1. CurrentDbの局所化と即座の解放: `CurrentDb` は呼び出すたびに新しいデータベースオブジェクトのインスタンスをヒープ上に生成する。これを変数に保持せず放置すれば、メモリリークの温床となる。
2. インデックス(Index)プロパティの明示的指定: `FindFirst` はテーブル形式(Table-type)のレコードセットでのみインデックスを利用できる。しかし、動的な条件指定やソートを伴う場合、ダイナセット(Dynaset-type)を使用せざるを得ないケースが多い。その場合でも、適切なプライマリキーやインデックスが定義されたクエリやテーブルをソースにすることで、エンジン側の最適化を引き出す。
3. オブジェクトの完全消去: `Recordset` および `Database` 変数は、スコープを抜ける前に必ず `Close` し、`Nothing` を代入してメモリを明示的に解放する。

—

3. 実用コード:ミリ秒を削る高速検索エンジン

以下に、現場で即座に採用できるプロダクション品質の検索クラス・プロシージャのコードを示す。この実装では、存在しないキーを検索した際の例外制御、複数条件の扱い、そしてメモリの厳格な管理を完全に網羅している。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 担当者マスタから指定されたコードを極限の速度で検索し、関連情報を取得する関数
‘ ==============================================================================
Public Function FastFindEmployee(ByVal targetEmployeeCode As String) As String

Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim resultValue As String

‘ 初期値の設定(見つからなかった場合のエラーメッセージ)
resultValue = “対象のレコードは存在しません。”

‘ 1. CurrentDbをローカル変数に格納し、オーバーヘッドを最小化
Set db = CurrentDb()

On Error GoTo ErrorHandler

‘ 2. ダイナセット型レコードセットのオープン(検索用に最適化)
‘ ※パフォーマンス確保のため、必要なフィールドのみ、かつインデックスが効くテーブル/クエリを指定
Set rs = db.OpenRecordset(“SELECT EmployeeCode, EmployeeName, IsActive FROM M_Employee WHERE DeletedFlag = 0”, dbOpenDynaset)

‘ 3. レコードが存在しない場合の早期リターン
If rs.EOF And rs.BOF Then
GoTo Cleanup
End If

‘ 4. FindFirstメソッドによる高速検索の実行
‘ 文字列型の場合はシングルクォーテーションで囲むことを忘れないこと
rs.FindFirst “EmployeeCode = ‘” & Replace(targetEmployeeCode, “‘”, “””) & “‘”

‘ 5. NoMatchプロパティによる判定
If rs.NoMatch Then
‘ 検索ヒットなしの処理
resultValue = “指定された社員コード: ” & targetEmployeeCode & ” は見つかりませんでした。”
Else
‘ 検索成功:フィールドからのデータ抽出
‘ ※必要に応じてブックマークの取得やデータの編集へ移行する
If rs!IsActive = True Then
resultValue = rs!EmployeeName & ” (稼働中)”
Else
resultValue = rs!EmployeeName & ” (休職中)”
End If
End If

Cleanup:
‘ 6. オブジェクトの明示的解放(逆順序の原則)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If

If Not db Is Nothing Then
db.Close ‘ CurrentDbで取得したオブジェクトであってもCloseは必須
Set db = Nothing
End If

FastFindEmployee = resultValue
Exit Function

ErrorHandler:
‘ 致命的なエラーのハンドリング
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “FastFindEmployee Error”
Resume Cleanup

End Function

—

4. チーフアーキテクトからの実践的提言

トランザクションとロックの罠

`FindFirst` でレコードを発見した後、そのままデータの更新(Edit / Update)に移行する場合は注意が必要だ。ダイナセットのデフォルトのロック動作は楽観的ロック(Optimistic Locking)に近い挙動を示すが、同時実行性が高い環境では、検索から更新の瞬間に他 usuarios(ユーザー)にレコードを書き換えられる「競合」が発生する。
もし厳密な排他制御が必要な場合は、`dbOpenTable` を用いたインデックスシーク(`Seek` メソッド)の採用を検討すべきである。ただし、`Seek` はテーブルオブジェクトに対してしか使えないため、複雑なWHERE句が必要なシーンでは今回の `FindFirst` が唯一にして最善の解となる。

SQLインジェクション対策の徹底

VBAにおける動的な文字列構築では、SQLインジェクションや構文エラーのリスクが常に伴う。上記のコード例のように、検索値にシングルクォーテーションが含まれる可能性を考慮し、必ず `Replace(targetEmployeeCode, “‘”, “””)` のようなサニタイジング(エスケープ処理)を挟むこと。これがプロとアマを分かつセキュリティ意識の境界線である。

総括

Access VBAは「おもちゃの言語」ではない。その背後にあるDAOとJet/ACEエンジンの挙動を完全に理解し、メモリ管理とインデックスの摂理に従ったコードを書くことで、数万〜数百万件のデータに対してもミリ秒単位で応答する堅牢なシステムを構築することが可能だ。

レガシーシステムの寿命を延ばし、パフォーマンスの限界を突破するための一手として、この `FindFirst` & `NoMatch` エンジンをあなたのアーキテクチャに組み込んでほしい。

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