【入門編】DAOのRecordsetを配列に一括格納:ループ処理を10倍速くするメモリ操作術 – Access VBA解析バイブル

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

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` を取り入れて、その爆速体験を味わってみてください!応援していますよ。

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