【入門編】DAOのRecordsetをExcelへ転送する際のメモリ管理と高速化 – Access VBA解析バイブル

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

速度の壁を打ち破る:DAO RecordsetからExcelへの「超高速」データ転送術

こんにちは。今日もコードと格闘しているあなた、素晴らしいですね。
Access VBAを学び始めると、誰もが一度は「壁」にぶつかります。それは、「プログラムは動くけれど、とにかく遅い」という問題です。

特に、Accessに溜まった大量のデータをExcelに書き出そうとした時、マクロの記録をベースに書いたコードでは、数千件のデータですら画面が固まり、終わるまでコーヒーを飲みに行けるほど時間がかかることがあります。

「もっとスマートに、プロらしく、一瞬で終わらせる方法はないのか?」

もしあなたがそう感じているなら、今日お伝えする内容は、あなたのエンジニア人生における大きな転換点になるはずです。今日は、単なる書き方のテクニックではなく、「メモリ」と「オブジェクト」の挙動を支配する、極限の高速化手法を伝授します。

ここをクリアすれば、Access VBAの基本はバッチリですよ。さあ、一緒に深淵を覗いてみましょう。

1. なぜあなたのコードは「遅い」のか? —— 通信コストの正体

まず、原因を知ることから始めましょう。初心者が陥りがちな「遅いコード」の典型は、以下のような書き方です。

' ❌ 遅いコードの典型例
Dim i As Long
i = 1
Do Until rs.EOF
    Cells(i, 1).Value = rs!氏名
    Cells(i, 2).Value = rs!金額
    i = i + 1
    rs.MoveNext
Loop

一見、論理的に正しいコードに見えますよね? でも、これは非常に「非効率」です。

なぜなら、`Cells(i, j).Value = …` という命令を出すたびに、VBAはExcelという巨大なアプリケーションに対して、「ねえ、このセルにこの値を書き込んで!」と一回ずつ通信(リクエスト)を送っているからです。

1万行のデータがあれば、1万回以上の通信が発生します。これを専門用語で「オブジェクトモデルへのオーバーヘッド」と呼びます。郵便局に、手紙を1通ずつ1万回届けに行くようなものです。非効率極まりないですよね。

プロのエンジニアは、こう考えます。
「1万通の手紙を、一つの大きなトラックに詰め込んで、一度に届けに行けばいいじゃないか」

この「トラック」の役割を果たすのが、「配列(Array)」なのです。

2. アーキテクチャの理解:DAOとADO、どちらを使うべきか

高速化の話に入る前に、少しだけ「道具」の使い分けについて触れておきましょう。Accessを扱う上で避けて通れないのが、DAOとADOの違いです。

特徴 DAO (Data Access Objects) ADO (ActiveX Data Objects)
得意領域 Access内部(Jet/ACEエンジン)の操作 SQL Server等の外部DBへの接続
速度 Access内部操作において極めて高速 ネットワーク越しのデータ取得に強い
特性 Accessの設計思想に最適化されている 汎用性が高く、標準的

今回の「Access内のデータをExcelへ」という用途であれば、DAOを使うのが定石です。Accessエンジンに最も近い場所で動作するため、メモリ効率が良く、今回紹介する高速化メソッドとの相性も抜群なのです。

3. 極限の高速化術:`GetRows` メソッドによる一括転送

では、いよいよ本題です。
DAOのRecordsetには、メモリ上にデータを一気に展開するための魔法のメソッド、`GetRows` が用意されています。

これを使うと、Recordsetの内容を「2次元配列」として一括で取得できます。セルに一つずつ書き込むのではなく、配列という「トラック」にデータを積み込み、最後にExcelの範囲(Range)へ「ドン!」と一括で流し込むのです。

実装コード:プロフェッショナル・テンプレート

以下のコードは、実務でそのまま使えるレベルの堅牢性と速度を兼ね備えています。

Option Explicit

''' <summary>
''' DAO Recordsetの内容をExcelシートへ高速に転送する
''' </summary>
Public Sub ExportDataToExcel()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim sql As String
    Dim dataArray As Variant
    Dim targetSheet As Worksheet
    Dim rowCount As Long
    Dim colCount As Long
    Dim i As Long, j As Long
    Dim finalArray As Variant

    ' --- 1. 環境設定 ---
    Set db = CurrentDb
    Set targetSheet = ThisWorkbook.Worksheets("出力先")
    sql = "SELECT 氏名, 金額, 部署 FROM T_売上データ" ' 出力したいSQL

    On Error GoTo ErrorHandler

    ' --- 2. データ取得 (DAO) ---
    Set rs = db.OpenRecordset(sql, dbOpenSnapshot) ' 読み取り専用で高速化

    ' データが空の場合の処理
    If rs.EOF Then
        MsgBox "対象データがありません。", vbExclamation
        GoTo CleanUp
    End If

    ' --- 3. 【極意】GetRowsによる一括メモリ展開 ---
    ' GetRowsは (列, 行) の順で配列を作成する特性があるため注意が必要
    dataArray = rs.GetRows

    ' 配列のサイズを取得
    colCount = UBound(dataArray, 1) + 1 ' 列数
    rowCount = UBound(dataArray, 2) + 1 ' 行数

    ' --- 4. 配列の次元入れ替え (Transpose処理) ---
    ' Excelのセル(行, 列)に合わせるため、(列, 行)を(行, 列)に変換する
    ' WorksheetFunction.Transposeは65536行の制限があるため、ループで自作するのがプロ流
    ReDim finalArray(1 To rowCount, 1 To colCount)
    
    For i = 1 To rowCount
        For j = 1 To colCount
            ' dataArray(列, 行) を finalArray(行, 列) へ
            finalArray(i, j) = dataArray(j - 1, i - 1)
        Next j
    Next i

    ' --- 5. Excelへの一括書き出し ---
    ' セル一つずつではなく、Rangeオブジェクトに対して一気に代入する
    Application.ScreenUpdating = False ' 画面更新を停止してさらに高速化
    
    targetSheet.Cells.ClearContents ' 事前にクリア
    
    ' ヘッダーの書き込み(必要に応じて)
    targetSheet.Range("A1:C1").Value = Array("氏名", "金額", "部署")
    
    ' データ本体を一括転送(ここが最速ポイント!)
    targetSheet.Range("A2").Resize(rowCount, colCount).Value = finalArray

    Application.ScreenUpdating = True
    MsgBox "転送完了! " & rowCount & " 件のデータを処理しました。", vbInformation

CleanUp:
    ' --- 6. ライフサイクル管理(メモリ解放) ---
    ' オブジェクトは必ず明示的に閉じて、Nothingを代入する
    On Error Resume Next
    If Not rs Is Nothing Then
        rs.Close
        Set rs = Nothing
    End If
    Set db = Nothing
    Application.ScreenUpdating = True
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

4. ここが重要!エンジニアが守るべき「3つの鉄則」

このコードを理解するために、絶対に外せないポイントが3つあります。

① `GetRows` の「次元の罠」を回避せよ

`GetRows` メソッドが返す配列は、実は私たちの直感とは逆の `(列, 行)` という構造になっています。
Excelのセルは `(行, 列)` です。
この違いを無視してそのまま貼り付けると、縦横が逆転したデータになってしまいます。コード内で `finalArray` を作り直して入れ替えているのは、この「次元の罠」を回避するためです。

② メモリ管理(オブジェクトのライフサイクル)

`Set rs = Nothing`。これを忘れないでください。
大量のデータを扱う際、Recordsetを閉じずに放置すると、メモリ上に「幽霊」のようにデータが残り続け、Accessが動作不安定になったり、最悪クラッシュしたりします。使い終わったら、必ず「後片付け」をする。これが一流の作法です。

③ `Application.ScreenUpdating = False`

これはExcel VBAにおける「おまじない」以上の意味を持ちます。
画面の描画更新を止めることで、Excelが「今、セルに書き込まれたから画面を書き直さなきゃ!」と考える無駄な時間を削ぎ落とします。これだけで体感速度が劇的に変わります。

最後に:一歩先のエンジニアへ

いかがでしたか?
「セルに一つずつ書く」という、当たり前だと思っていた手法が、実はどれほど大きなコストを支払っていたかを感じ取っていただけたでしょうか。

プログラミングの本質は、単に「動くものを作る」ことではありません。「コンピュータの仕組み(メモリ、通信、オブジェクト)を理解し、いかに効率よく命令を伝えるか」にあります。

今回学んだ「配列による一括処理」は、Accessに限らず、あらゆる言語やシステムに通じる普遍的な考え方です。これをマスターしたあなたは、もう「マクロの記録」の段階を完全に脱却しています。

自信を持って、もっと複雑で、もっと大きな課題に挑戦していってください。応援していますよ!

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