Access VBAの壁を突破せよ:`GetRows`メソッドでループ処理を10倍速くする「メモリ操作術」
こんにちは。Access VBAの深淵へようこそ。
「マクロの記録」から一歩踏み出し、VBAでコードを書き始めたあなた。おそらく、こんな壁にぶつかったことはありませんか?
「コードは完璧に書けたはずなのに、データ件数が増えると処理が信じられないほど遅くなる……」
数千件、数万件のデータをループ(`Do Until rs.EOF`)で回して、計算したり別の処理をしたりしているとき、画面がフリーズしたようになり、終わるまでコーヒーを何杯も飲めてしまう。そんな経験です。
実は、その遅さの原因は、あなたの論理的なミスではありません。「データの持ち方(アーキテクチャ)」の理解不足によるものなのです。
今日は、プロのエンジニアが必ずと言っていいほど実践している、DAO Recordsetを配列に一括格納して爆速化するテクニックを伝授します。これをマスターすれば、あなたは「マクロの延長」を卒業し、真の「業務自動化エンジニア」への階段を駆け上がることになります。
1. なぜ、あなたのループは遅いのか?(ボトルネックの正体)
まず、なぜ `Do Until rs.EOF` … `rs.MoveNext` という書き方が遅いのか、その「裏側」を理解しましょう。
「お店(ディスク/エンジン)」と「キッチン(メモリ)」の比喩
あなたが料理(データ処理)をしていると想像してください。
- Recordset(DAO)は、「スーパーマーケットの棚」です。
- ループ処理は、「一品ずつ買い物に行って、キッチンへ戻る」作業です。
`rs!氏名` と書くたびに、VBAはデータベースエンジンに対して「ねえ、次の行の『氏名』をちょうだい!」とリクエストを投げます。この「リクエストして、受け取る」という通信(オーバーヘッド)が、ループのたびに発生するのです。
たとえデータがメモリ上にあったとしても、DAOのRecordsetオブジェクトを介してアクセスする以上、必ず「オブジェクトのプロパティを参照する」という重い処理が介在します。これが、数万回繰り返されると、膨大な時間差となって現れるのです。
プロの考え方:一括買い出し(Bulk Load)
プロのエンジニアはこう考えます。
「一品ずつ買い物に行くのは無駄だ。トラックを一台用意して、必要な食材を全部まとめてキッチン(メモリ)に運び込もう!」
この「トラックによる一括運び込み」を実現するのが、今回紹介する `GetRows` メソッド です。
2. 救世主 `GetRows` メソッドの威力
`GetRows` メソッドを使うと、Recordsetの中身を丸ごと 「Variant型の配列」 に一瞬で流し込むことができます。
一度メモリ(配列)の中にデータが展開されてしまえば、そこからはデータベースエンジンとの通信は一切発生しません。CPUがメモリ上の数値を読み取るだけの、極めて高速な処理へと変わります。
劇的なスピードアップのイメージ図
【従来の方式:Recordsetループ】
`VBA` $\xrightarrow{リクエスト}$ `DBエンジン` $\to$ `データ取得` $\xrightarrow{返却}$ `VBA` (これを数万回繰り返す)
$\Rightarrow$ 通信コストが積み重なり、激重!
【GetRows方式:配列操作】
`VBA` $\xrightarrow{一括リクエスト}$ `DBエンジン` $\to$ `全データ一括返却` $\xrightarrow{格納}$ `VBAのメモリ(配列)`
$\Rightarrow$ 通信は1回だけ。あとはメモリ内での高速計算!
3. 実戦コード:Before & After
それでは、実際のコードを見てみましょう。どちらが速いか、体感してみてください。
Option Compare Database
Option Explicit
' ============================================================
' 比較用テスト関数
' ============================================================
Public Sub CompareSpeed()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim i As Long
Dim startTime As Double
Set db = CurrentDb
' テスト用のテーブル(大量のデータが入っている前提)
Set rs = db.OpenRecordset("T_SampleData", dbOpenSnapshot)
If rs.EOF Then
MsgBox "データがありません。"
Exit Sub
End If
' --- パターンA: 従来のRecordsetループ (遅い) ---
Debug.Print "--- Pattern A Start ---"
startTime = Timer
Do Until rs.EOF
' ここで各フィールドにアクセスするたびにオーバーヘッドが発生
Dim dummy As Variant
dummy = rs!Amount * 1.1
rs.MoveNext
Loop
Debug.Print "Pattern A Time: " & Format(Timer - startTime, "0.000") & " sec"
' --- パターンB: GetRowsによる配列処理 (爆速) ---
Debug.Print "--- Pattern B Start ---"
startTime = Timer
' 【重要】Recordsetの中身を丸ごと配列に格納
Dim dataArray As Variant
dataArray = rs.GetRows
' 配列のループ処理(メモリ内操作なので超高速)
' GetRowsで取得した配列は [列, 行] の2次元配列になる点に注意!
Dim rowIdx As Long
Dim colIdx As Long
Dim totalRows As Long
Dim totalCols As Long
totalRows = UBound(dataArray, 2) ' 行数の上限
totalCols = UBound(dataArray, 1) ' 列数の上限
For rowIdx = 0 To totalRows
For colIdx = 0 To totalCols
' 列番号 0 が "Amount" フィールドだと仮定
Dim dummyFast As Variant
dummyFast = dataArray(0, rowIdx) * 1.1
Next colIdx
Next rowIdx
Debug.Print "Pattern B Time: " & Format(Timer - startTime, "0.000") & " sec"
rs.Close
Set rs = Nothing
Set db = Nothing
MsgBox "比較完了!Debugウィンドウを確認してください。"
End Sub
4. 【超重要】初心者が必ずハマる「配列の罠」
`GetRows` を使ったとき、多くの人が一度は頭を抱えます。それは、「配列のインデックス(添字)の構造」 です。
通常の感覚だと、配列は `Array(行, 列)` の順で並んでいると考えますよね?
しかし、`GetRows` が返す配列は、`Array(列, 行)` という、いわゆる「列優先(Column-major)」に近い構造になっています。
ここを間違えるとどうなるか?
- `dataArray(行, 列)` と書いてしまう $\to$ 「インデックスが範囲外です」エラーで停止
- または、全くデタラメな値を取得してしまう
正しいアクセス方法
' GetRowsで取得した配列の構造
' dataArray(0, 0) -> 1行目の1列目
' dataArray(0, 1) -> 2行目の1列目
' dataArray(1, 0) -> 1行目の2列目
' 正解のループ順序
For r = 0 To UBound(dataArray, 2) ' 行のループ
For c = 0 To UBound(dataArray, 1) ' 列のループ
Debug.Print dataArray(c, r) ' (列, 行) の順番で指定!
Next c
Next r
ここをクリアできれば、あなたはもう「配列の扱いに慣れた人」です。
5. まとめ:エンジニアへの第一歩
今回の極限の知見をまとめましょう。
1. Recordsetのループは「通信」が発生する重い処理である。
2. `GetRows` を使って、データを一括で「メモリ(配列)」に運び込む。
3. 配列操作はメモリ内で行われるため、劇的に高速化する。
4. 注意点:`GetRows` の配列は `(列, 行)` の順序である!
「動けばいい」という段階から、「効率的に、美しく、高速に動く」という段階へ。
このアーキテクチャの視点を持つことが、Access VBAを掌握する鍵となります。
最初は配列の添字(インデックス)でエラーが出るかもしれません。でも大丈夫、それはあなたが「メモリ操作」という一段高いステージに足を踏み入れた証拠ですから。
さあ、今すぐあなたのコードに `GetRows` を取り入れて、その爆速体験を味わってみてください!応援していますよ。
