【実務・中級編】DAOのFindFirstメソッドとADOのFindメソッド:検索速度と柔軟性の比較 – Access VBA解析バイブル

スポンサーリンク

【Access VBA極限解説】DAO `FindFirst` vs ADO `Find`:数万件の荒海を最速で渡るための処方箋

開発現場でよく見かける光景がある。
「とりあえず動くから」という理由で、数万件規模のローカルテーブルに対し、非効率なレコードセットの走査や不適切な検索メソッドを実装し、画面がフリーズしたかのような遅延を引き起こすコード。

業務システムの寿命を縮めるのは、往々にしてこうした「思考停止したデータベースアクセス」だ。

Access VBAにおけるデータ操作の二大巨頭、DAO (Data Access Objects) と ADO (ActiveX Data Objects)。
今回は、その核心機能の一つである、DAOの `FindFirst` メソッドと、ADOの `Find` メソッドの「検索速度」「柔軟性」「メモリとライフサイクルの挙動」を徹底的に解剖する。

実務で明日から使える、堅牢かつ最速のプロダクションコードまで一気通貫で伝授しよう。

—

1. アーキテクチャの根本思想を知る

まず、両者のDNAを理解しなければならない。ここをきわめておかないと、適切なツール選択はできない。

  • DAO (`FindFirst`):
  • Access(JET / ACEエンジン)のネイティブな子孫。
  • テーブルやネイティブなアタッチデータベースに対して、エンジン内部のインデックスを最大限に活かして爆速でヒットする。
  • ただし、条件式の記述作法がAccess SQLのそれに厳密に従う必要がある。
  • ADO (`Find`):
  • OLEDBプロキシを介した汎用的なデータアクセス層。
  • Recordsetオブジェクト上で動作し、文字列による柔軟な条件指定(ワイルドカードや複数条件の組み合わせ)が可能。
  • しかし、Jet/ACEプロバイダ経由でのローカル操作において、巨大なデータセットを扱う場合のオーバーヘッドはDAOに軍配が上がることが多い。

結論から言えば、「Accessローカルの単一ファイル内での高速検索ならDAOの独壇場」「複雑な条件や将来的なSQL Server等への移行視野ならADOの表現力」という棲み分けになる。

—

2. 検索速度とメモリの現実(実測の知見)

10万件の受発注データから特定の伝票番号を割り出す処理を想定してほしい。

DAOの `FindFirst` は、検索対象フィールドにインデックスが貼られている場合、O(log n) のオーダーで瞬時にポインタを該当レコードへ移動させる。内部キャッシュのヒット率も極めて高い。

一方、ADOの `Find` は、クライアントサイドカーソル(`adUseClient`)上でメモリ上に展開されたレコードセットをスキャンするため、インデックスの効き方がADOプロバイダの実装に依存し、大規模データではDAOよりもメモリを圧迫しやすい。

実務における判断基準:

  • DAOの `FindFirst`: インデックスが効いたローカルMDB/ACCDBの高速検索においては無類の強さを誇る。
  • ADOの `Find`: サーバーサイドの重いクエリを投げるのではなく、一度取得したクライアント側キャッシュに対して柔軟な絞り込みをかけたい場合に真価を発揮する。

—

3. 【プロダクションコード】堅牢性を極めた実装例

現場でコピー&ペーストしてそのまま使える、エラーハンドリングと言語仕様のベストプラクティスを網羅したコードを提示する。

今回は、「DAOによる高速な条件検索」と「ADOによる柔軟な条件検索」の両方を実装し、それぞれの作法がいかに厳格であるべきかを示す。

パターンA:DAO `FindFirst` による堅牢な検索プロシージャ

Option Explicit

Public Sub SearchDataByDAO(ByVal targetID As Long)
‘ —————————————————————–
‘ 目的: DAOのFindFirstを用いた高速レコード検索
‘ 前提条件: 対象テーブルのIDフィールドにインデックスが存在すること
‘ —————————————————————–
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim criteria As String

On Error GoTo ErrorHandler

‘ カレントデータベースの参照を取得(Newは不要、CurrentDbは関数)
Set db = CurrentDb()

‘ パフォーマンスとメモリ効率を考慮し、ダイナセットまたはスナップショットを開く
‘ 検索のみであれば dbOpenSnapshot を推奨
Set rs = db.OpenRecordset(“T_Orders”, dbOpenSnapshot)

If rs.RecordCount = 0 Then
MsgBox “テーブルにデータが存在しません。”, vbExclamation, “DAO検索”
GoTo CleanUp
End If

‘ DAOの条件式構築(文字列連結によるSQLインジェクションや型不一致に注意)
criteria = “OrderID = ” & targetID

‘ 検索実行
rs.FindFirst criteria

If rs.NoMatch Then
MsgBox “指定されたID: ” & targetID & ” のレコードは見つかりませんでした。”, vbInformation, “DAO検索”
Else
‘ ヒットした場合の処理
MsgBox “レコード発見! 顧客名: ” & rs.Fields(“CustomerName”).Value, vbInformation, “DAO検索”
‘ 必要に応じて編集モードへ(Snapshotの場合はRecordsetを開き直す必要あり)
End If

CleanUp:
‘ オブジェクトのライフサイクル管理:必ず逆順で明示的に解放する
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “DAO Error”
Resume CleanUp
End Sub

パターンB:ADO `Find` による柔軟な条件検索プロシージャ

Option Explicit

Public Sub SearchDataByADO(ByVal customerNameKeyword As String)
‘ —————————————————————–
‘ 目的: ADOのFindを用いた柔軟な部分一致検索
‘ 特徴: クライアントサイドカーソル上でワイルドカード検索が可能
‘ —————————————————————–
Dim conn As Object
Dim rs As Object
Dim criteria As String

On Error GoTo ErrorHandler

‘ 接続オブジェクトの生成
Set conn = CreateObject(“ADODB.Connection”)
conn.Open “Provider=Microsoft.ACE.OLEDB.12.0;Data Source=” & CurrentDb.Name

‘ レコードセットオブジェクトの生成
Set rs = CreateObject(“ADODB.Recordset”)

‘ クライアントサイドカーソルを指定(Findメソッドを使用するため必須)
rs.CursorLocation = 3 ‘ adUseClient

‘ データ取得(全件ではなく、必要に応じWHERE句を入れるのが定石だが、Findのデモとして全件取得)
rs.Open “SELECT OrderID, CustomerName, OrderDate FROM T_Orders”, conn, 1, 3 ‘ adOpenKeyset, adLockOptimistic

If rs.EOF And rs.BOF Then
MsgBox “データが存在しません。”, vbExclamation, “ADO検索”
GoTo CleanUp
End If

‘ ADOのFind条件式(LIKE演算子とワイルドカード ” が使用可能)
‘ 例: “CustomerName LIKE ‘%株式会社%'” (OLEDBの場合、ワイルドカードは ” が標準)
criteria = “CustomerName LIKE ‘” & customerNameKeyword & “‘”

‘ 検索の方向を指定して実行 (adSearchForward = 1)
rs.Find criteria, 1, 1 ‘ Criteria, SkipRows, SearchDirection

‘ 該当レコードが見つかったかどうかの判定(EOFに達していないか)
Do While Not rs.EOF
Debug.Print “ヒット: ID=” & rs.Fields(“OrderID”).Value & “, 顧客名=” & rs.Fields(“CustomerName”).Value

‘ 複数ヒットをさらに次へ進めて検索する場合
rs.Find criteria, 1, 1 ‘ 1行スキップして次を検索
Loop

CleanUp:
If Not rs Is Nothing Then
If rs.State = 1 Then rs.Close
Set rs = Nothing
End If
If Not conn Is Nothing Then
If conn.State = 1 Then conn.Close
Set conn = Nothing
End If
Exit Sub

ErrorHandler:
MsgBox “ADO処理中にエラーが発生しました: ” & Err.Description, vbCritical, “ADO Error”
Resume CleanUp
End Sub

—

4. チーフアーキテクトからの警鐘:実務で絶対に避けるべきアンチパターン

1. 「とりあえず `CurrentDb` を乱用する」
`CurrentDb` メソッドは、呼び出すたびに新しい `Database` オブジェクトのインスタンスをメモリ上に生成する。ループ内で `CurrentDb.OpenRecordset` を呼ぶようなコードを書いた瞬間、メモリリークとパフォーマンス低下の爆弾を抱えることになる。データベース参照は必ず変数に格納し、使い回せ。
2. DAOでの文字列連結によるSQLインジェクション・型エラー
`FindFirst criteria` の `criteria` を組み立てる際、数値型と文字列型(特にシングルクォートのエスケープ)を怠ると、ランタイムエラーや最悪の場合は不正な挙動の原因になる。パラメータ化クエリが使えないDAOの `FindFirst` においては、入力値のバリデーションを厳格に行うこと。
3. オブジェクトの解放漏れ
VBAのガベージコレクションを信用するな。`Set rs = Nothing`、`Set db = Nothing` を明示しないコードは、Accessの肥大化(Bloat)を加速させ、mdb/accdbファイルそのものを破壊するリスクを高める。

—

まとめ

Access VBAにおけるデータベース操作は、単に「動けばいい」の世界ではない。
数万件、数十万件のデータと対峙する業務システムにおいて、DAOとADOの特性を見極め、適切な検索メソッドを選択する眼力こそが、プロフェッショナルとアマチュアを分かつ境界線だ。

  • ローカルでインデックスを活かした爆速ピンポイント検索 なら DAO `FindFirst`
  • メモリ上での柔軟な部分一致や高度な条件フィルタリング なら ADO `Find`

この選択基準を胸に刻み、明日からのコードをより美しく、より強靭なものへとアップデートしてほしい。

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