【実務・中級編】DAOのFindFirstとADOのFilter:検索速度を比較し最適なメソッドを選択する – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

DAOのFindFirstか、ADOのFilterか。――検索エンジンの「重み」を知り、設計に魂を込める

業務システムの開発現場において、「動けばいい」という思考停止が最も恐ろしい。

特にAccess VBAを用いた開発において、データ量が増大した瞬間にシステムが沈黙する、あるいは極端にレスポンスが悪化する現象の多くは、エンジニアが「オブジェクトのライフサイクル」と「検索メカニズムのアーキテクチャ」を理解していないことに起因する。

今回のテーマは、中級者への登竜門とも言える「DAOのFindFirst」と「ADOのFilter」の使い分けだ。
単なる速度比較で終わらせるつもりはない。なぜそのメソッドが速いのか、なぜその設計が破綻を招くのか。プロフェッショナルとして、その判断基準をここに叩き込む。

1. アーキテクチャの本質的違い:ネイティブか、ラッパーか

まず、君たちが使っている二つの武器の「出自」を理解せよ。ここを間違えると、どんなにコードを書き換えてもパフォーマンスは改善しない。

DAO (Data Access Objects)

DAOは、Accessのエンジンである「ACE/Jetエンジン」と密結合したネイティブなオブジェクトだ。データベースのページ構造を直接、あるいは極めて近いレイヤーで操作する。
いわば、その土地の言葉を母国語として話す「地元住民」だ。ローカルの `.accdb` や `.mdb` を扱うなら、DAOの右に出る者はいない。

ADO (ActiveX Data Objects)

ADOは、あらゆるデータベース(SQL Server, Oracle, MySQL等)に共通のインターフェースを提供するための抽象化レイヤー(ラッパー)だ。
これは「外交官」だ。異なる国の言語(プロトコル)を仲介する能力には長けているが、ローカルな通信においては、その「翻訳プロセス」がオーバーヘッド(余計な負荷)となる。

2. FindFirst vs Filter:メカニズムの深淵

検索メソッドの動作原理を解剖する。

DAO.FindFirst:ポインタの移動

`FindFirst` は、レコードセット内を走査し、条件に合致する最初のレコードを見つけた瞬間、「カーソル(現在地を示すポインタ)」をその位置へ移動させるだけの操作だ。
インデックスが適切に貼られていれば、B-Tree構造を辿るだけで済むため、計算量は極めて小さく、メモリ消費も最小限で済む。

ADO.Filter:ビューの再構築

一方で、ADOの `Filter` プロパティは性質が全く異なる。
`Filter` を適用した瞬間、ADOは条件に合致するレコードを抽出した「新しいビュー(論理的なサブセット)」をメモリ上に構築しようとする。
つまり、検索ではなく「データの再定義」に近い。データ量が増えれば増えるほど、この「ビューの構築」に要するコストは指数関数的に増大する。

特徴 DAO.FindFirst ADO.Filter
主な用途 特定の1件を高速に特定する 条件に合うレコード群を抽出する
動作原理 カーソル(ポインタ)の移動 新しいレコードセット(ビュー)の作成
インデックス効果 極めて高い 限定的(フィルタリングに依存)
メモリ負荷 極めて低い データ量に応じて増大する

3. 実務における「黄金の判断基準」

設計者として、君たちは以下のフローチャートに従ってメソッドを選択すべきだ。

1. 接続先はどこか?

  • ローカルのAccessファイル $\rightarrow$ DAOを優先せよ。
  • 外部SQL Server等 $\rightarrow$ ADOを選択せよ。

2. 目的は何か?

  • 「特定のIDを持つ1件」を探す $\rightarrow$ `FindFirst` (DAO) を使え。
  • 「特定の条件に合う複数件」を一覧表示したい $\rightarrow$ SQLの `WHERE` 句を使え。
  • *注意:ADOを使っている場合でも、`Filter` プロパティではなく、最初から `SELECT … WHERE …` で取得するのが鉄則だ。*

4. プロダクション品質のコード実装

では、実際の現場でそのまま使える、堅牢な実装例を示す。
単に動くだけではない。エラーハンドリング、オブジェクトの解放、そしてパフォーマンスを意識した設計だ。

【パターンA】DAOによる超高速な単一レコード検索

大量のデータから、特定のキーを持つレコードを瞬時に特定する際の標準的な実装。

''
' 指定したIDに基づいてレコードを高速に特定する
' @param db         : 開いているDAO.Databaseオブジェクト
' @param targetID   : 検索したい一意のID
' @return           : 見つかった場合はTrue、見つからない場合はFalse
''
Public Function FindSingleRecordByID(ByRef db As DAO.Database, ByVal targetID As Long) As Boolean
    Dim rs As DAO.Recordset
    Dim strSQL As String
    
    On Error GoTo ErrorHandler
    
    ' 1. 必要なフィールドのみを抽出するSQL(全フィールド取得は避ける)
    strSQL = "SELECT ID, CustomerName FROM T_Customers"
    Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
    
    ' 2. FindFirstによるポインタ移動
    ' ※ID列にインデックスが貼られていることが前提
    rs.FindFirst "ID = " & targetID
    
    If Not rs.NoMatch Then
        ' 見つかった場合の処理
        Debug.Print "Found: " & rs![CustomerName]
        FindSingleRecordByID = True
    Else
        ' 見つからなかった場合
        FindSingleRecordByID = False
    End If

CleanUp:
    ' 3. オブジェクトの確実な解放(メモリリーク防止)
    If Not rs Is Nothing Then
        rs.Close
        Set rs = Nothing
    End If
    Exit Function

ErrorHandler:
    MsgBox "Error in FindSingleRecordByID: " & Err.Description, vbCritical
    FindSingleRecordByID = False
    Resume CleanUp
End Function

【パターンB】ADOによる外部DB連携(Filterは使わない)

外部サーバーからデータを取得する場合、`Filter` プロパティに頼るのは二流だ。サーバー側で絞り込みを行わせる(Server-side filtering)のが一流の設計だ。

''
' SQL Server等の外部DBから条件に合うデータを取得する
' @param connectionString : 接続文字列
' @param categoryID      : フィルタリング条件
''
Public Sub FetchRemoteData(ByVal connectionString As String, ByVal categoryID As Integer)
    Dim conn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim strSQL As String
    
    On Error GoTo ErrorHandler
    
    Set conn = New ADODB.Connection
    conn.Open connectionString
    
    ' 【重要】Filterプロパティではなく、SQLのWHERE句でサーバー側に負荷を逃がす
    ' これにより、ネットワーク経由で転送されるデータ量を最小化する
    strSQL = "SELECT OrderID, OrderDate FROM T_Orders WHERE CategoryID = " & categoryID
    
    Set rs = New ADODB.Recordset
    ' CursorTypeは用途に応じて適切に選択(adOpenStatic, adOpenForwardOnly等)
    rs.Open strSQL, conn, adOpenStatic, adLockReadOnly
    
    Do Until rs.EOF
        Debug.Print rs!OrderID, rs!OrderDate
        rs.MoveNext
    Loop

CleanUp:
    ' リソースの破棄
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
        Set rs = Nothing
    End If
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then conn.Close
        Set conn = Nothing
    End If
    Exit Sub

ErrorHandler:
    MsgBox "Critical Error in FetchRemoteData: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

5. 結論:エンジニアとしての矜持

`DAO.FindFirst` と `ADO.Filter`。
これらは単なる「検索メソッド」ではない。それぞれが、メモリ、CPU、ネットワーク帯域に対して異なる「コスト」を要求する、設計上の選択肢だ。

  • ローカルの高速性を追求するなら DAO を選べ。
  • データ量が増えることを予見し、インデックスを設計せよ。
  • 外部接続なら ADO を使い、フィルタリングは SQL 側に任せよ。

「なんとなく動く」コードを書くのは今日で終わりにしろ。
アーキテクチャの意図を理解し、意図的にメソッドを選択できるエンジニアだけが、真に堅牢でスケーラブルなシステムを構築できるのだ。

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