Recordsetのループを捨て、メモリを支配せよ:GetRowsによる爆速データ処理アーキテクチャ
「コードは動いている。しかし、データ件数が増えると目に見えて遅くなる」
もし君が、`Do Until rs.EOF` から始まる `MoveNext` のループを、当たり前のように業務ツールに組み込んでいるとしたら、君の設計は「動くコード」であっても「プロの設計」ではない。
Access VBAにおけるパフォーマンスのボトルネックの正体は、多くの場合、アルゴリズムの複雑さではなく、「VBAエンジン」と「データベースエンジン(ACE/Jet)」の間の過剰な通信(コンテキストスイッチ)にある。
本記事では、Recordsetのカーソル移動によるオーバーヘッドを完全に排除し、処理速度を劇的に向上させる極限のテクニック、`GetRows` メソッドによる「配列一括変換」の設計思想を伝授する。
1. なぜ `.MoveNext` は遅いのか:アーキテクチャの視点
まず、君が書いているそのループが、なぜ「遅い」のかをロジカルに理解してほしい。
DAOやADOのRecordsetをループで回す際、VBAは一歩進むごとに以下のプロセスを繰り返している。
1. VBAエンジンが「次のレコードへ」と命令を出す。
2. データベースエンジンが、物理的なデータファイル(.accdb等)から該当するレコードを特定する。
3. データベースエンジンが、そのレコードのデータをメモリ上に展開する。
4. データベースエンジンからVBAエンジンへ、データが渡される。
1万件のデータがあれば、この「境界を越える通信」が1万回発生する。これがカーソル移動のオーバーヘッドだ。たとえ1件あたりの通信がマイクロ秒単位であっても、累積すれば致命的な遅延となる。
プロのアーキテクトは、この通信回数を「1回」に抑え込む。それが `GetRows` メソッドの役割だ。
2. GetRowsによる「一括転送」のメカニズム
`GetRows` メソッドは、Recordsetが保持している全レコード(または指定した件数)を、一撃でメモリ上の2次元配列へと放り込む。
これにより、データベースエンジンとの通信は「1回」で終了する。残りの処理は、VBAが最も得意とする「メモリ上の配列操作」に完全に委ねられる。この「通信の最小化」こそが、速度を10倍以上に跳ね上げる鍵だ。
【警告】GetRowsの「罠」を回避せよ
`GetRows` は強力だが、初心者が必ず陥る罠が2つある。
1. インデックスの順序が逆:
通常の感覚では `Array(行, 列)` だと思うだろう。しかし、`GetRows` が返す配列は `Array(列, 行)` である。ここを間違えると、デバッグに膨大な時間を溶かすことになる。
2. メモリ・フットプリント:
数百万件のデータを一度に `GetRows` すれば、メモリ不足(Out of Memory)でアプリケーションは即座にクラッシュする。データ量に応じた「バッチ処理」の検討が必要だ。
3. 実戦投入:堅牢な高速データ取得モジュール
以下に、実務のプロダクションコードとしてそのまま使える、堅牢な実装例を示す。
単に `GetRows` を使うだけでなく、エラーハンドリングと、扱いやすい「行・列」形式への変換を考慮した設計にしている。
Option Explicit
''' <summary>
''' DAO.Recordsetのデータを、扱いやすい「行・列」形式の2次元配列に一括変換する。
''' </summary>
''' <param name="rs">ソースとなるDAO.Recordset</param>
''' <returns>Variant型の2次元配列 [行, 列]。データがない場合はEmptyを返す。</returns>
Public Function GetRecordsetAsArray(ByRef rs As DAO.Recordset) As Variant
On Error GoTo ErrorHandler
' 1. Recordsetの状態チェック
If rs Is Nothing Then
GetRecordsetAsArray = Empty
Exit Function
End If
If rs.EOF And rs.BOF Then
GetRecordsetAsArray = Empty
Exit Function
End If
' 2. GetRowsによる一括取得
' 注意: GetRowsは [列, 行] の形式で配列を返す
Dim rawData As Variant
rawData = rs.GetRows()
' 3. 配列の次元取得
Dim colCount As Long: colCount = UBound(rawData, 1)
Dim rowCount As Long: rowCount = UBound(rawData, 2)
' 4. [列, 行] から [行, 列] へ組み替える(プログラマの使いやすさ優先)
' これにより、通常のループ処理 own[i, j] が可能になる
Dim processedArray As Variant
ReDim processedArray(0 To rowCount, 0 To colCount)
Dim r As Long, c As Long
For r = 0 To rowCount
For c = 0 To colCount
processedArray(r, c) = rawData(c, r)
Next c
Next r
GetRecordsetAsArray = processedArray
Exit Function
ErrorHandler:
' ログ出力などのエラーハンドリングをここに実装
Debug.Print "Error in GetRecordsetAsArray: " & Err.Description
GetRecordsetAsArray = Empty
End Function
''' <summary>
''' 実装テスト用メインルーチン
''' </summary>
Public Sub TestHighSpeedProcess()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim dataArray As Variant
Dim i As Long, j As Long
Set db = CurrentDb
' テスト用のSQL(実際の実務に合わせて変更)
Set rs = db.OpenRecordset("SELECT * FROM T_LargeData", dbOpenSnapshot)
' --- 高速化の核心 ---
' Recordsetをループせず、一括で配列へ
dataArray = GetRecordsetAsArray(rs)
' Recordsetは即座に閉じる(メモリ解放とロック回避)
rs.Close
Set rs = Nothing
' 配列が取得できたか確認
If IsEmpty(dataArray) Then
MsgBox "データが存在しません。", vbInformation
Exit Sub
End If
' --- メモリ上での高速計算・加工処理 ---
' ここでのループは、データベースへの通信が発生しないため極めて高速
For i = LBound(dataArray, 1) To UBound(dataArray, 1)
' 例: 1列目の値に基づいたロジック
' If dataArray(i, 0) > 100 Then ...
Next i
MsgBox "処理完了。取得件数: " & (UBound(dataArray, 1) + 1) & " 件", vbInformation
Set db = Nothing
End Sub
4. アーキテクトからの設計指針
このテクニックを導入する際、君が守るべき「設計の鉄則」を伝える。
① 「読み取り」と「書き込み」を分離せよ
`GetRows` は読み取り専用の高速化手法だ。配列に取り込んだ後は、データベースとの接続を一旦断ち切る(`rs.Close`)のが理想的な設計である。
「配列でデータを加工」→「加工が終わった後に、新しいRecordsetを開いて一括で書き込む」という、Read-Process-Writeの分離を徹底せよ。これにより、トランザクションの範囲を最小限に抑え、データベースのロック競合を防ぐことができる。
② メモリ使用量の予測
`GetRows` は全データをメモリに載せる。
例えば、1行が1KBのデータが10万行ある場合、それだけで約100MBのメモリを消費する。Access(32bit版)のメモリ空間には限界があることを忘れるな。データ件数が極端に多い(例:数百万件)場合は、`GetRows` の引数に件数を指定し、例えば「1万件ずつ取得して処理する」というチャンク(Chunk)処理を設計に組み込むべきだ。
③ 型の安全性
`GetRows` で取得した配列には、データベースの `Null` が含まれる可能性がある。VBAの配列内で `Null` を扱うと、型不一致のエラーを招きやすい。
配列から値を取り出す際は、`Nz()` 関数のようなガードを常に意識するか、あるいは型変換を厳格に行う設計にせよ。
結論
`MoveNext` によるループは、小規模なツールなら許容される。しかし、エンタープライズレベルの業務システムを構築するのであれば、それは「怠慢」に等しい。
「通信を最小化し、メモリを支配する」
この思想をコードに落とし込めるかどうかが、単なるプログラマーと、信頼されるアーキテクトを分かつ境界線である。次回の開発からは、迷わず `GetRows` を選択せよ。
