こんにちは! Access VBAの開発現場で、日夜クエリの山やパフォーマンスのチューニングと格闘していませんか?
今回は、Access VBAのパフォーマンスを劇的に引き上げるための、知る人ぞ知る極上のテクニック「DAO.RecordsetのFilterプロパティを使ったメモリ上での高速絞り込み」について解説します。
「データを条件で絞り込む」というと、どうしてもクエリ(QDF)を毎回新しく作ったり、SQL文を組み立て直したりしがちですよね。でも、実はそれ、Accessのエンジン(ACE)にとって結構な重労働なんです。
ここをクリアすれば、Access VBAの基本はバッチリですよ!
一時クエリを生成する無駄なコストを削ぎ落とし、メモリ上で電光石火のデータ処理を実現するスマートな手法を、一緒にマスターしていきましょう。
—
1. なぜ「一時クエリの生成」は遅いのか?
AccessでVBAからデータを抽出する際、よく見かけるのがこんなコードです。
‘ 駄目な例:やりがちなアプローチ
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
‘ 条件が変わるたびにSQLを発行してクエリを開く
Set rs = db.OpenRecordset(“SELECT FROM T_受注 WHERE 顧客ID = ” & txtID.Value)
一見、何の問題もないように見えますよね。しかし、このコードが実行されるたび、Accessの裏側では以下のドラマが起きています。
1. SQLの解析(パース): 「このSQL文の意味は何か」をデータベースエンジンが毎回熟考します。
2. 実行計画の作成: 「どのインデックスを使うのが最速か」を毎回ゼロから計算します。
3. ディスク/システムリソースの消費: 一時的なオブジェクトの生成と破棄が発生します。
これが数件なら気になりませんが、ループ処理の中で何度もこれをやったり、数万件のレコードを相手にしたりすると、途端にアプリが「モッサリ」動き出す原因になります。
—
2. 救世主:`Recordset.Filter` プロパティとは?
ここで登場するのが、DAOが持つ `Filter` プロパティ です。
この手法の哲学はシンプルです。
「最初にテーブル(または重たいクエリ)全体、あるいは大きめの塊を一度だけメモリに読み込み、あとは `Filter` でスパッと視界を狭める」
イメージとしては、こういう感じです。
- 通常クエリ: 毎回、倉庫の奥から品物をごっそり引っ張り出してくる(遅い)
- Filterプロパティ: 手元の机の上にダンボール箱を1つドンと置き、「今から見るのはこの条件に合うものだけね」と下敷きで隠す(超高速)
一度メモリ(RAM)上に乗ったデータに対するフィルタリングは、ディスクアクセスを伴わないため、体感できるほどのスピード差を生み出します。
—
3. 実践! `Filter` と `OpenRecordset` の華麗なる連携
それでは、実際にコードを見てみましょう。
今回は、「顧客ID」でデータを高速に絞り込み、合致したレコードを処理する実用的なコード例です。
Sub FilterSpeedUpSample()
Dim db As DAO.Database
Dim rsBase As DAO.Recordset
Dim rsFiltered As DAO.Recordset
Dim targetCustomerID As Long
targetCustomerID = 105 ‘ 検索したい顧客ID(例)
‘ 1. 現在のデータベースへの参照を取得
Set db = CurrentDb
‘ 2. 【重要】まずはベースとなる全レコードセットをメモリ上に取得
‘ ※条件によっては必要な列だけに絞ったSQLでもOKです
Set rsBase = db.OpenRecordset(“T_受注”, dbOpenSnapshot)
‘ 3. Filterプロパティに条件を設定する
‘ ※SQLの WHERE 句と同じ要領で記述できます(ただし “WHERE” は不要)
rsBase.Filter = “顧客ID = ” & targetCustomerID
‘ 4. フィルタが適用された「子レコードセット」を生成する
Set rsFiltered = rsBase.OpenRecordset()
‘ 5. データの処理
If rsFiltered.RecordCount = 0 Then
MsgBox “該当するデータはありませんでした。”, vbExclamation
Else
rsFiltered.MoveFirst
Do Until rsFiltered.EOF
‘ ここでデータを処理(例:イミディエイトウィンドウに出力)
Debug.Print “受注ID: ” & rsFiltered!受注ID & ” / 金額: ” & rsFiltered!金額
rsFiltered.MoveNext
Loop
End If
‘ 6. クリーンアップ(オブジェクトの開放はプロの基本です)
rsFiltered.Close
rsBase.Close
Set rsFiltered = Nothing
Set rsBase = Nothing
Set db = Nothing
MsgBox “処理が完了しました!”, vbInformation
End Sub
コードの重要ポイント解説
1. `dbOpenSnapshot` の活用
ベースのレコードセットを開く際、`dbOpenSnapshot`(読み取り専用スナップショット)を指定しています。データを更新する必要がない参照目的であれば、排他制御やロックのオーバーヘッドが消えるため、これだけでも劇的に軽くなります。
2. `rsBase.Filter = “…”`
ここに条件を指定します。文字列型なら `1 = ‘東京’` のようにシングルクォーテーションで囲むなど、通常のSQLの条件式と同じルールが使えます。
3. `Set rsFiltered = rsBase.OpenRecordset()`
ここが魔法の瞬間です。`Filter`プロパティを設定した後に、もう一度 `.OpenRecordset()` を呼ぶことで、フィルタ済みの結果だけを持った新しいレコードセット(子)が誕生します。
—
4. 陥りやすい罠とエンジニアの心得
この `Filter` プロパティ、非常に強力ですが、現場で初心者がハマりがちな「落とし穴」がいくつかあります。ここを押さえておけば完璧です。
罠その1: フィルタ後のレコード数(RecordCount)の罠
フィルタをかけた直後の `rsBase.RecordCount` を見ても、正確な数は返ってきません(DAOの仕様上、`-1` または元の全レコード数が返ることがあります)。
必ず、`OpenRecordset()` で子レコードセットを作ってから、そちらの `RecordCount` を見るようにしてください。
罠その2: 日付や文字列のクォーテーション忘れ
SQLと同様に、データ型に応じたエスケープが必要です。
- 数値型: `1 = ” & num`
- 文字列型: `1 = ‘” & str & “‘`
- 日付型: `1 = #” & Format(dt, “yyyy/mm/dd”) & “#`(Access/DAOでは日付は `#` で囲むのが鉄則です)
—
おわりに:道具を使いこなすプロへ
「クエリを毎回組み立てる」方法から、「メモリ上でフィルタをかける」方法へシフトするだけで、あなたの書くAccess VBAはワンランク上の「プロのコード」に生まれ変わります。
Accessは古いデータベースエンジンだと言われることもありますが、その特性やオブジェクトのライフサイクルを正しく理解して使えば、実務の現場でこれほど頼りになる相棒もいません。
ぜひ、日々のマクロやフォームの処理にこのテクニックを取り入れて、サクサク動く快適なシステムを作ってみてくださいね。あなたの開発ライフを、これからも応援しています!
