【入門編】DAOのRecordsetを「配列」に一括変換して処理する:ループ処理の速度を10倍にするテクニック – Access VBA解析バイブル

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

Access VBAの壁を突破せよ:Recordsetを「配列」に一括変換して処理速度を10倍にする極意

こんにちは。Access VBAの世界へようこそ。

「マクロの記録からは卒業したけれど、なんだか処理が重い……」
「ループ処理を書いているけれど、データが増えると数分待たされる……」

もしあなたが今、そんな壁にぶつかっているとしたら、おめでとうございます。あなたは今、「プログラマとしての真のステップアップ」の入り口に立っています。

多くの開発者が「ロジック(計算式や条件分岐)」を改善しようと躍起になりますが、実はAccess VBAのパフォーマンスにおいて、もっと根本的で、もっと劇的な改善ポイントが存在します。

それが、「データの運び方(アクセス・アーキテクチャ)」です。

今日は、DAOのRecordsetを「配列」へと一括変換し、ループ処理の速度を劇的に向上させるテクニック、`GetRows`メソッドの真髄を伝授しましょう。ここをクリアすれば、あなたのAccessアプリケーションは別次元のスピードを手に入れます。

1. なぜ、あなたのループは遅いのか?(オブジェクトの重みを知る)

まず、多くの人が書いてしまう「標準的なループ処理」を見てみましょう。

' --- 遅いコードの典型例 ---
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT * FROM T_売上データ")

Do Until rs.EOF
    ' 一行ごとに、オブジェクトのプロパティへアクセスしている
    Debug.Print rs!商品名 & " : " & rs!金額
    rs.MoveNext
Loop
rs.Close

一見、完璧に見えますよね? しかし、このコードには「見えないコスト」が大量に隠れています。

「一粒ずつ運ぶ」という非効率

Recordsetは、データベースのエンジンと対話するための「重厚なオブジェクト」です。
`rs!商品名` と書くたびに、VBAは「Recordsetオブジェクトよ、今の行の『商品名』というフィールドの値を教えてくれ」と、データベースエンジンに対して通信(メソッド呼び出し)を要求します。

これを「一粒ずつお米を運ぶ」作業に例えてみましょう。
1. 炊飯器(データベース)へ行く
2. お米を1粒手に取る
3. テーブル(VBA)へ戻る
4. 次の1粒のために、また炊飯器へ行く……

これを1万回繰り返したらどうなるでしょうか? 途方もない時間がかかりますよね。これが、ループ処理が遅くなる正体です。

2. 救世主 `GetRows`:一括転送の魔法

プロのエンジニアは、こう考えます。
「一粒ずつ運ぶのが無駄なら、大きなバケツで一気に運べばいいじゃないか」

その「バケツ」の役割を果たすのが、Recordsetメソッドの `GetRows` です。

`GetRows`を使うと、Recordsetにある全データを、一瞬にして「メモリ上の配列(Variant型)」へと流し込むことができます。

「バケツで一気に運ぶ」イメージ

1. 炊飯器(データベース)へ行く
2. 大きなバケツを用意して、全データを一気にすくい取る
3. テーブル(VBA)へ戻る
4. テーブルの上で、バケツの中身を処理する

メモリ上(配列)での処理は、データベースエンジンへの通信を一切必要としません。CPUが直接メモリを読み書きするだけなので、爆速なのです。

3. 実践! `GetRows` を使いこなす最強のテンプレート

では、実際にどのように書くのか。現場でそのまま使える、堅牢なコード例を見てみましょう。

Sub FastProcessingWithGetRows()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim dataArray As Variant ' GetRowsの結果を受け取るための入れ物
    Dim i As Long, j As Long
    
    Set db = CurrentDb
    ' 1. Recordsetを開く
    Set rs = db.OpenRecordset("SELECT 商品名, 金額 FROM T_売上データ", dbOpenSnapshot)
    
    ' 【重要】データが空の場合の処理(これを忘れるとエラーになります!)
    If rs.EOF Then
        MsgBox "処理するデータがありません。"
        rs.Close
        Exit Sub
    End If
    
    ' 2. 【ここが極意】GetRowsで全データを一括して配列に格納
    ' 引数を指定しない場合は、全レコードを配列に格納します
    dataArray = rs.GetRows
    
    ' 3. Recordsetはもう不要なので、すぐに閉じて解放する
    ' これにより、データベースのロック時間を最小限に抑えられます
    rs.Close
    Set rs = Nothing
    
    ' 4. 配列を使って高速ループ処理
    ' 注意:GetRowsで作られる配列は「行と列」が逆転した「2次元配列」になります
    ' 配列の構造: dataArray(列インデックス, 行インデックス)
    
    Debug.Print "--- 処理開始 ---"
    
    ' LBound/UBoundを使って、配列の範囲を正確に取得する
    ' LBound(dataArray, 2) は「最初の行」
    ' UBound(dataArray, 2) は「最後の行」
    For i = LBound(dataArray, 2) To UBound(dataArray, 2)
        ' 列の指定は、SELECT文で指定した順番(0から始まる)
        ' 0: 商品名, 1: 金額
        Debug.Print "商品: " & dataArray(0, i) & " | 金額: " & dataArray(1, i)
    Next i
    
    Debug.Print "--- 処理終了 ---"
    
    Set db = Nothing
End Sub

4. 陥りやすい「2つの罠」を回避せよ

`GetRows`は強力ですが、初心者の方が必ずと言っていいほどハマる落とし穴が2つあります。

罠①:配列の「次元」が逆転している!

ここが最大の難所です。通常のプログラミングでは `Array(行, 列)` という順序が一般的ですが、`GetRows`が生成する配列は `Array(列, 行)` という形式になります。

  • あなたの直感: `dataArray(行番号, 列番号)`
  • 現実の構造: `dataArray(列インデックス, 行インデックス)`

もし間違えると、「インデックスが有効範囲にありません」というエラーが出るか、あるいは全くデタラメな値を参照することになります。
「GetRowsの配列は、縦と横が入れ替わっている」と呪文のように覚えておきましょう。

罠②:空のRecordsetに手を出すと爆発する

データが0件のRecordsetに対して `GetRows` を実行すると、実行時エラーが発生します。
必ず、`If rs.EOF Then` を使って、「データが存在するかどうか」を確認するガード句を入れましょう。これはプロの作法です。

5. まとめ:アーキテクチャの視点を持とう

今回のテクニックをまとめます。

1. Recordsetのループは「通信」の繰り返しである。 通信コストは非常に高い。
2. `GetRows` は「一括転送」の手段である。 データをメモリ(配列)へ一気に移す。
3. 配列処理は「メモリ内計算」である。 オブジェクト操作に比べ、圧倒的に高速。
4. 配列の添字(インデックス)に注意。 `(列, 行)` の順序を忘れない。

「プログラムを速くしたい」と思ったとき、コードの書き方を工夫する前に、「今、自分はデータの塊をどう扱っているか?」というアーキテクチャの視点を持ってみてください。

この視点が持てるようになれば、あなたはもう単なる「マクロ使い」ではありません。システム全体を設計できる「エンジニア」の仲間入りです。

一歩ずつ、着実に。
あなたのVBAライフが、より素晴らしいものになることを応援しています!

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