【入門編】ADOでSQL Serverのテーブル値を一括取得:配列への格納による高速なデータ処理 – Access VBA解析バイブル

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

「ループの壁」を超えろ。ADO `GetRows` で実現する、爆速データ処理の極意

こんにちは。現場の設計から実装まで、数々のシステムを組み上げてきたチーフアーキテクトです。

Access VBAを学び始めると、多くの人が最初にぶつかる「壁」があります。それは「処理速度」です。
「マクロの記録から一歩踏み出して、自分でコードを書き始めたけれど、データ件数が増えた途端に動きが重くなってしまった……」
そんな悩みを聞くことがよくあります。

もしあなたが、`Do Until rs.EOF` のようなループの中で、1行ずつ値を読み取って処理をしているとしたら……。残念ながら、あなたはまだ「データの海」を小さなスプーンで掬い上げている状態です。

今日は、SQL Serverなどの外部データベースから大量のデータを取得する際、その「スプーン」を「巨大なコンテナ船」に一変させる、プロフェッショナルな手法「GetRowsメソッドによる配列への一括転送」を伝授します。

ここをマスターすれば、あなたのAccessアプリケーションは、プロの現場でも通用する「爆速」のツールへと進化します。準備はいいですか?

1. なぜあなたのコードは遅いのか?(DAOとADOのアーキテクチャ)

まず、根本的な原因を知ることから始めましょう。

Accessでデータを扱うとき、主に2つの道具(ライブラリ)を使います。
1. DAO (Data Access Objects): Access内部のテーブル(.accdb)を操作するのに最適化された、Accessの「地元の道具」です。
2. ADO (ActiveX Data Objects): SQL Serverなどの外部サーバーにあるデータベースと通信するための、世界標準の「遠征用道具」です。

SQL Serverに接続する場合、私たちはADOを使います。ここで問題になるのが「通信のオーバーヘッド」です。

「スプーン」と「コンテナ船」の理論

想像してみてください。向こうの島(SQL Server)から、大量のリンゴ(データ)をこちらに運ぶとき、どうすれば効率的でしょうか?

  • ループ処理 (`While Not rs.EOF`):

一人の作業員が、小さなスプーンでリンゴを1個ずつ持って、橋を往復するイメージです。データが1万件あれば、1万回も橋を渡らなければなりません。これが「遅さ」の正体です。

  • GetRowsメソッド:

巨大なコンテナ船にリンゴを詰め込み、一気に橋を渡ってこちらに届けるイメージです。橋を渡る回数はたったの1回。これが「速さ」の正体です。

2. 極意:`GetRows` メソッドによる一括転送

ADOの `Recordset` オブジェクトには、`GetRows` という非常に強力なメソッドが用意されています。これは、レコードセット内の全データを、一瞬で「メモリ上の配列」へ放り込む魔法のコマンドです。

実装のイメージ図

通常のループと、GetRowsの違いを視覚的に理解しましょう。

【通常のループ】
`SQL Server` $\xrightarrow{1件}$ `VBA (Recordset)` $\xrightarrow{1件}$ `VBA (変数)` $\dots$ (これを繰り返す)

【GetRowsによる一括取得】
`SQL Server` $\xrightarrow{全データ一括}$ `VBA (メモリ上の配列)`

3. 実践コード:SQL Serverから爆速でデータを取得する

それでは、実際に現場で使えるレベルのコードを見ていきましょう。
このコードは、SQL Serverに接続し、データを配列に格納して、メモリ上で高速に処理する一連の流れを網羅しています。

Option Compare Database
Option Explicit

''' <summary>
''' SQL Serverからデータを一括取得し、配列内で処理するサンプル
''' </summary>
Public Sub FastDataFetchExample()
    ' --- 1. 変数宣言 ---
    Dim conn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim dataArray As Variant ' GetRowsの結果を受け取るための動的配列
    Dim connectionString As String
    Dim sql As String
    
    Dim i As Long, j As Long
    Dim rowCount As Long
    Dim colCount As Long

    ' --- 2. 接続設定 (環境に合わせて書き換えてください) ---
    connectionString = "Provider=MSOLEDBSQL;Data Source=サーバー名;Initial Catalog=データベース名;Integrated Security=SSPI;"
    sql = "SELECT EmployeeID, EmployeeName, Salary FROM Employees"

    On Error GoTo ErrorHandler

    ' --- 3. 接続とデータ取得 ---
    Set conn = New ADODB.Connection
    conn.Open connectionString

    Set rs = New ADODB.Recordset
    ' OpenメソッドでSQLを実行
    rs.Open sql, conn, adOpenStatic, adLockReadOnly

    ' データが存在するかチェック
    If Not rs.EOF Then
        ' ★ここが最大のポイント!★
        ' Recordsetの内容を丸ごと配列に転送します。
        ' これにより、これ以降の処理ではデータベースへの通信が発生しません。
        dataArray = rs.GetRows
    Else
        MsgBox "データが見つかりませんでした。", vbInformation
        GoTo CleanUp
    End If

    ' --- 4. 配列内での高速処理 ---
    ' GetRowsで取得した配列は「(列, 行)」の形式(列優先)になることに注意!
    ' 非常に重要:rs.Fields.Count が「列」の数、rs.RecordCount が「行」の数です。
    colCount = UBound(dataArray, 1) ' 列の最大インデックス
    rowCount = UBound(dataArray, 2) ' 行の最大インデックス

    Debug.Print "--- 取得完了: " & (rowCount + 1) & "件のデータを処理します ---"

    ' メモリ上でのループは、DBへのアクセスに比べて数千倍速いです
    For i = 0 To rowCount ' 行のループ
        For j = 0 To colCount ' 列のループ
            ' ここでデータ加工や計算を行います
            ' 例: Salary(インデックス2)が50万以上なら特定ラベルを付けるなど
            Debug.Print "Row:" & i & " Col:" & j & " Value:" & dataArray(j, i)
        Next j
    Next i

    MsgBox "処理が完了しました。メモリ上で高速に処理できました。", vbInformation

CleanUp:
    ' --- 5. 後始末 (オブジェクトの解放) ---
    ' これを忘れると、接続が残り続け、サーバーに負荷をかけます
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
        Set rs = Nothing
    End If
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then conn.Close
        Set conn = Nothing
    End If
    Exit Sub

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

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

この手法は強力ですが、初心者が必ずと言っていいほどハマる罠があります。これを知っているかどうかが、エンジニアとしての格の違いを生みます。

罠①:配列の次元が「逆」である

ここが最大の難所です。通常、私たちは「行・列」の順でデータを考えますが、`GetRows` が返す配列は `dataArray(列, 行)` という構造になっています。

  • `dataArray(0, 0)` $\rightarrow$ 1行目の1列目
  • `dataArray(1, 0)` $\rightarrow$ 1行目の2列目
  • `dataArray(0, 1)` $\rightarrow$ 2行目の1列目

もし `dataArray(行, 列)` と書いてしまうと、`Subscript out of range`(インデックスが範囲外です)というエラーで即死します。必ず `dataArray(列, 行)` と意識してください。

罠②:メモリの使いすぎ(全件取得の恐怖)

「とりあえず全部 `GetRows` しちゃえ!」は危険です。
SQL Serverに数百万件のレコードがある場合、それを一気に配列に入れようとすると、Access(PC)のメモリを食いつぶし、最悪の場合クラッシュします。

  • 対策: `WHERE` 句を使って取得件数を絞るか、必要に応じて分割して取得する設計を心がけましょう。

罠③:Null値の扱い

データベースのフィールドに `Null` が含まれている場合、配列に格納された後も `Null` として扱われます。
VBAの文字列変数などに直接代入しようとすると、型不一致エラーが出ることがあります。
`If IsNull(dataArray(j, i)) Then …` のように、必ずチェックする癖をつけましょう。

まとめ:ここをクリアすれば、Access VBAの基本はバッチリですよ

今回学んだ内容を振り返りましょう。

1. ADO を使い、外部サーバー(SQL Server)と効率よく通信する。
2. `While Not rs.EOF` によるループは、通信回数が増えるため低速である。
3. `GetRows` を使って、データを一括でメモリ上の 配列 に転送する。
4. 配列は `(列, 行)` の順序でアクセスする。

「データベースからデータを取ってくる」フェーズと、「取ってきたデータを加工する」フェーズを明確に分ける。これが、プロフェッショナルなアーキテクチャの第一歩です。

最初は配列の次元に戸惑うかもしれません。でも、一度このスピード感を体感してしまえば、もう元のループ処理には戻れなくなるはずです。

一歩ずつ、着実に。あなたのコードが、より洗練されたものになることを応援しています!

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