VBAで大量のデータを処理する際の処理速度を比較してみた

VBAで大量のデータを処理する際の処理速度を比較してみた

目次

はじめに

業務でVBAを使用する際には、概して膨大なデータを扱います。
特に「膨大なCSVデータを処理する際、どのような方法で処理を行うのが最も早いのか?」というのを今回のテーマとします。

検証用データの準備

まず、検証用データを作成します。とはいっても今回のテーマは「膨大なデータ」です。
数万件のデータを手打ちで準備するのは現実的ではないので、これもVBAで作成してしまうことにしました。
今回は、下記のように「ID(通番)」「名前」「地域」「売上」の項目がすべて埋められた10万行+見出しのデータを用意します。

それぞれ1行ずつ作成するマクロでも良いのですが、処理に時間がかかるので、配列を使用します。

Sub CreateDummyData()
    Dim startTime As Double
    startTime = Timer

    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    Dim ws As Worksheet
    Set ws = ActiveSheet
    ws.Cells.Clear ' シートを初期化
    
    ws.Range("A1:D1").Value = Array("ID", "名前", "地域", "売上")
    
    Dim maxRows As Long
    maxRows = 100000
    
    Dim dataArray() As Variant
    ReDim dataArray(1 To maxRows, 1 To 4)
    
    Dim regions As Variant
    regions = Array("東京", "大阪", "名古屋", "福岡", "札幌")
    
    Dim i As Long
    Randomize
    For i = 1 To maxRows
        dataArray(i, 1) = i                              ' ID
        dataArray(i, 2) = "ユーザー_" & i                ' 名前
        dataArray(i, 3) = regions(Int(Rnd() * 5))        ' 地域 (ランダム)
        dataArray(i, 4) = Int((Rnd() * 9000) + 1000)     ' 売上 (1000~10000のランダム)
    Next i
    
    ws.Range("A2").Resize(maxRows, 4).Value = dataArray
    
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    
    MsgBox "ダミーデータ作成が完了しました。" & vbCrLf & _
           "処理時間: " & Format(Timer - startTime, "0.00秒"), vbInformation
End Sub

上記のようなコードとなりました。
早速、Excel上で実行してみます。

このような結果となりました。
スクリーンショット上には写っていませんが、きちんと10万件のデータが作成されています。
今回は、このデータを使って処理速度の検証を行うことにしましょう。

今回は、それぞれ検証のため、検証結果_RowLoop検証結果_Filter検証結果_Arrayという名前の空白シートを準備してください。※下記のコードは、シートが作成されていないとエラーとなります。

パターン1. 「行ループ」のコード

Sub Test_RowLoop()
    Dim startTime As Double
    startTime = Timer

    Dim wsSource As Worksheet: Set wsSource = Sheets(1)
    Dim wsDest As Worksheet: Set wsDest = Sheets("検証結果_RowLoop")
    
    wsDest.Cells.Clear

    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    ' ヘッダーのコピー
    wsSource.Range("A1:D1").Copy wsDest.Range("A1")

    Dim lastRow As Long
    lastRow = wsSource.Cells(wsSource.Rows.Count, "A").End(xlUp).Row
    
    Dim i As Long, destRow As Long
    destRow = 2
    
    ' 1行ずつセルを確認してコピペ
    For i = 2 To lastRow
        If wsSource.Cells(i, 3).Value = "東京" Then
            wsSource.Range(wsSource.Cells(i, 1), wsSource.Cells(i, 4)).Copy wsDest.Cells(destRow, 1)
            destRow = destRow + 1
        End If
    Next i

    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic

    MsgBox "【通常の行ループ】処理完了" & vbCrLf & _
           "処理時間: " & Format(Timer - startTime, "0.000秒"), vbInformation
End Sub

パターン2. 「AutoFilter」のコード

Sub Test_AutoFilter()
    Dim startTime As Double
    startTime = Timer

    Dim wsSource As Worksheet: Set wsSource = Sheets(1) ' ダミーデータシート
    Dim wsDest As Worksheet: Set wsDest = Sheets("検証結果_Filter")
    
    ' 出力先シートの初期化
    wsDest.Cells.Clear

    ' 高速化設定
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    ' オートフィルターを実行して「東京」を抽出
    wsSource.Range("A1").AutoFilter Field:=3, Criteria1:="東京"
    
    ' 可視セル(抽出された行)のみをコピーして貼り付け
    wsSource.UsedRange.SpecialCells(xlCellTypeVisible).Copy wsDest.Range("A1")
    
    ' フィルターを解除
    wsSource.AutoFilterMode = False

    ' 設定を元に戻す
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic

    MsgBox "【AutoFilter】処理完了" & vbCrLf & _
           "処理時間: " & Format(Timer - startTime, "0.000秒"), vbInformation
End Sub

パターン3. 「配列ループ」のコード

Sub Test_ArrayLoop()
    Dim startTime As Double
    startTime = Timer

    Dim wsSource As Worksheet: Set wsSource = Sheets(1)
    Dim wsDest As Worksheet: Set wsDest = Sheets("検証結果_Array")
    
    wsDest.Cells.Clear

    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    Dim lastRow As Long
    lastRow = wsSource.Cells(wsSource.Rows.Count, "A").End(xlUp).Row
    Dim sourceArray As Variant
    sourceArray = wsSource.Range("A1:D" & lastRow).Value

    Dim destArray() As Variant
    ReDim destArray(1 To lastRow, 1 To 4)
    
    ' ヘッダーを格納
    Dim col As Long
    For col = 1 To 4
        destArray(1, col) = sourceArray(1, col)
    Next col

    ' ループ処理
    Dim i As Long, destRow As Long
    destRow = 2 ' 2行目から格納開始
    
    For i = 2 To lastRow
        If sourceArray(i, 3) = "東京" Then
            destArray(destRow, 1) = sourceArray(i, 1) ' ID
            destArray(destRow, 2) = sourceArray(i, 2) ' 名前
            destArray(destRow, 3) = sourceArray(i, 3) ' 地域
            destArray(destRow, 4) = sourceArray(i, 4) ' 売上
            destRow = destRow + 1
        End If
    Next i

    ' シートへ書き出し
    If destRow > 2 Then
        wsDest.Range("A1").Resize(destRow - 1, 4).Value = destArray
    End If

    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic

    MsgBox "【配列ループ】処理完了" & vbCrLf & _
           "処理時間: " & Format(Timer - startTime, "0.000秒"), vbInformation
End Sub

検証結果

パターン1. 「行ループ」の検証結果

行ループでの検証結果は下記の通りの結果になりました。
予想していたことではありますが、1行ずつの処理は非効率で、とても時間がかかっています。
出力はしっかりできていましたが、検証データよりもっと膨大なデータを扱う実務環境では現実的な方法ではないことがわかります。

パターン2. 「AutoFilter」での検証結果

やはり、行ループに比べ、圧倒的に早かったです。これなら実務でも問題ありません。
※結果は行ループと同じだったのでスクリーンショットは処理時間のみとします。

パターン3. 「配列ループ」での検証結果

さて、次に配列ループですが、AutoFilterを超える速度で処理完了となりました。

検証結果のまとめ

「行ループ」の場合は、10万回シートを見に行き、10万回シートに書き込むというやり取りのため、単純に時間がかかっています。人間に例えるなら、1行1行指し示し目視で確認しながら書き写しているようなものです。
それと比較して、AutoFilterはExcel側のフィルター機能を利用するので、十分に早いと言えますし、配列に関しては、Excel側とのやり取りが読み込みと書き出しのみなので、圧倒的に早くなります。
大量のデータ処理は、いかにExcel側とのやり取りを減らすかが重要だと考えられますね。

おわりに

今回は検証ダミーデータの作成から、速度検証までを実際に行ってみました。
特にExcelや、Excelで加工したいCSVのデータは膨大になりがちなので、処理速度については重要なテーマだと考えています。
AutoFilterの手軽さを取るか、配列の堅さを取るかは、それぞれの業務によって異なると思いますが、都度できるだけ最適な選択を目指したいですね。

中村涼子