【実務・中級編】DAO.Indexオブジェクトを使用して、一時テーブルの検索速度を劇的に向上させる – Access VBA解析バイブル

スポンサーリンク

【Access VBA極限最適化】一時テーブルにDAO.Indexを爆誕させ、大量データ検索を100倍速にする技術

こんにちは。開発プロジェクトの現場で数々のAccessレガシーシステムを救ってきたチーフアーキテクトだ。

Access VBAで数万件以上のレコードを扱うバッチ処理を書いたとき、こんな絶望を味わったことはないだろうか。
「処理が遅い。ループの中で `DLookup` や `Find1st` を回したら、終わる頃にはコーヒーが冷めきっているどころか定時を過ぎている」

あなたは何の疑いもなく、こうコードを書いたはずだ。
`CurrentDb.OpenRecordset(“SELECT FROM テンポラリテーブル WHERE …”)`

ちょっと待て。その一時テーブル、「インデックス(Index)」を作っているか?
「えっ、一時テーブルにインデックスなんて貼れるの?」「そもそもAccessでインデックスって、設計画面から手動でやるもんでしょ?」

──甘い。プロのエンジニアを名乗るなら、DAO(Data Access Objects)を使い倒して、VBAのコード片だけでインデックスを動的に爆誕させろ。
今回は、一時テーブルの検索・突合処理を極限まで加速させるプロフェッショナルなインデックス戦略を伝授する。

なぜ「インデックスなしの一時テーブル」は遅いのか?(非効率の正体)

まず、Access(JET/ACEデータベースエンジン)の裏側の動きを理解しよう。

インデックスのないテーブルに対して検索(Seek や Find 系、あるいはSQLでの結合・抽出)を行うと、データベースエンジンは「フルテーブルスキャン(全件走査)」を行う。つまり、10万件のデータがあれば、1件目から10万件目までを上から順にすべて舐め回す。これをループ内で何千回も繰り返せば、CPUは悲鳴を上げ、処理が重くなるのは必然だ。

ここで「じゃあ、最初からリンクテーブルや永続テーブルにインデックスを貼っておけばいいじゃないか」と思うかもしれないが、それはナンセンスだ。
マルチユーザー環境において、永続テーブルをワークエリア(一時保存場所)として使うのは、「レコードロックの競合」「Bloat(データベースの肥大化)」「ゴミデータの残留」という百害あって一利なしのバグの温床になる。

正解は、「VBAの実行時にローカルの一時テーブルを動的生成し、そこにインデックスを貼って高速検索し、用が済んだら秒で捨てる」だ。

現場で使える! DAO.Index 動的生成のプロダクションコード

百聞は一見に如かず。実際に、一時テーブルを作成し、そこにプライマリキーとインデックスをプログラムから流し込み、爆速で検索・処理を行う完全なプロシージャを公開しよう。

このコードは、エラーハンドリング、既存テーブルのクリーンアップ、DAOによるインデックス構築の作法をすべて網羅した、そのまま現場に投入できる実戦仕様だ。

Option Compare Database
Option Explicit

‘ =========================================================================
‘ 処理名 : 爆速一時テーブル処理サンプリング
‘ 概要 : 一時テーブルを動的作成し、DAO.Indexを付与して高速にデータ処理を行う
‘ =========================================================================
Sub ExecuteOptimizedBatchProcess()
Dim db As DAO.Database
Dim td As DAO.TableDef
Dim idx As DAO.Index
Dim rstSource As DAO.Recordset
Dim rstTemp As DAO.Recordset

Dim startTime As Double
startTime = Timer

Set db = CurrentDb

On Error GoTo ErrorHandler

‘ —————————————————————–
クリーンアップ(既存の一時テーブルがあれば削除)
‘ —————————————————————–
‘ Note: DoCmd.DeleteObjectはUIに依存するため、TableDefsコレクションから安全に削除する
On Error Resume Next
db.TableDefs.Delete “tmp_TargetData”
On Error GoTo ErrorHandler

‘ —————————————————————–
1. 一時テーブルの作成(DAOによるDDL実行)
‘ —————————————————————–
‘ ※パフォーマンスを最大化するため、適切なデータ型を明示してCREATE TABLEする
db.Execute “CREATE TABLE tmp_TargetData (” & _
“CustomerID Long, ” & _
“OrderDate DateTime, ” & _
“Amount Currency, ” & _
“Status Text(50));”, dbFailOnError

‘ —————————————————————–
2. 【最重要】DAO.Indexの動的追加
‘ —————————————————————–
Set td = db.TableDefs(“tmp_TargetData”)

‘ プライマリキー(またはユニークインデックス)の作成
Set idx = td.CreateIndex(“PrimaryKey”)
idx.Fields.Append idx.CreateField(“CustomerID”)
idx.Primary = True
‘ 補足: 複数フィールドのインデックスにする場合は、ここに idx.Fields.Append idx.CreateField(“OrderDate”) を追加する
td.Indexes.Append idx

‘ 追加の検索用インデックス(Statusフィールドなど、絞り込みによく使う項目)
Set idx = td.CreateIndex(“idx_Status”)
idx.Fields.Append idx.CreateField(“Status”)
td.Indexes.Append idx

‘ インデックス定義をテーブルに確定反映させる
td.Indexes.Refresh

‘ —————————————————————–
3. データ投入(トランザクションによる高速化)
‘ —————————————————————–
db.BeginTrans
Set rstTemp = db.OpenRecordset(“tmp_TargetData”, dbOpenDynaset)

‘ ここでは擬似的にマスターなどからデータを一括INSERT、あるいはRecordsetで流し込む
‘ 大量データの場合はINSERT INTO SQLを使う方が圧倒的に速いが、
‘ 今回はDAO Recordsetを使った例として記述する
Dim i As Long
For i = 1 to 50000
rstTemp.AddNew
rstTemp!CustomerID = i
rstTemp!OrderDate = Date – (i Mod 30)
rstTemp!Amount = i 1.5
rstTemp!Status = IIf(i Mod 2 = 0, “Complete”, “Pending”)
rstTemp.Update
Next i

db.CommitTrans
rstTemp.Close

‘ —————————————————————–
4. インデックスを活用した高速検索(Seekメソッドの利用)
‘ —————————————————————–
‘ ※Seekを使うには、テーブルタイプ(dbOpenTable)のレコードセットを開く必要がある
Set rstTemp = db.OpenRecordsets(“tmp_TargetData”, dbOpenTable)

‘ 検索に使用するインデックスを指定
rstTemp.Index = “PrimaryKey”

‘ 特定の顧客IDを爆速で検索(フルスキャンとは比較にならない速度)
rstTemp.Seek “=”, 12345

If Not rstTemp.NoMatch Then
Debug.Print “見つかりました: CustomerID = ” & rstTemp!CustomerID & “, 金額 = ” & rstTemp!Amount
Else
Debug.Print “該当データなし”
End If

MsgBox “処理完了。実行時間: ” & Format(Timer – startTime, “0.00”) & “秒”, vbInformation

CleanUp:
‘ —————————————————————–
5. 確実なリソース解放
‘ —————————————————————–
If Not rstTemp Is Nothing Then rstTemp.Close: Set rstTemp = Nothing
If Not rstSource Is Nothing Then rstSource.Close: Set rstSource = Nothing
Set td = Nothing
Set idx = Nothing
Set db = Nothing
Exit Sub

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

このアーキテクチャが「堅牢」である理由(プロのこだわり)

上記のコードには、単に動くだけではない、現場のトラブルを防ぐための厳格な設計思想が組み込まれている。

1. `dbOpenTable` と `Seek` メソッドの組み合わせ

ADOや通常のDAO Dynasetでの検索(`FindFirst` など)は裏でクエリが走るため遅い。しかし、DAOのテーブル型レコードセット(`dbOpenTable`)に対して `.Index = “インデックス名”` を指定し `.Seek` を実行すると、JET/ACEエンジンのB-Treeインデックスに直接ヒットするため、数万件の中から一瞬(0.00秒台)でレコードを釣り上げることができる。

2. トランザクション(`BeginTrans` / `CommitTrans`)の包摂

大量のレコードを `AddNew` / `Update` する際、トランザクションを張らないと、Accessは1レコード追加するたびにディスクへの書き込み(物理I/O)を発生させ、激遅になる。トランザクションで囲むことで、メモリ上で処理を完結させ、爆発的な速度向上とデータの整合性を両立できる。

3. 一時テーブルの適切な破棄と肥大化防止

Accessで最も恐ろしいのは、テーブルの作成と削除を繰り返すことでデータベースファイルが物理的に肥大化(Bloat)する現象だ。今回はコードレベルで綺麗に `TableDefs.Delete` を行っているが、もし実務でこれを長期間・高頻度で回す場合は、定期的な `CompactDatabase`(最適化)の運用設計も視野に入れておくべきだ。

チーフアーキテクトからの提言

「Accessだから遅い」「VBAだから限界だ」という言い訳は、今日で終わりだ。
ツールの性能が出ないのは、データベースの仕組み(インデックスとアクセスパスの理論)を無視したコードを書いている人間の責任である。

今回紹介した 「動的一時テーブル作成 + DAO.Index 構築 + Seekによる高速アクセス」 の手法をモノにすれば、ExcelやAccessの枠を超えた、実用性とスピードを兼ね備えた堅牢な業務システムを構築できる。

あなたの書くコードを、今日から「プロフェッショナル」へと格上げしてほしい。

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