Excel VBA SUMIFの高速化

高速化

Excel関数のSUMIFをVBAで高速化する方法をご説明します。

VLOOKUP関数やCOUNTIF関数同様に、SUMIF関数もデータ量が多いと処理時間が長くなります。

この「重たい関数」のSUMIFの処理を高速でVBAで再現したいと思います。

VLOOKUP関数と違いSumif関数はExcel2016でも、速度改善されていないようです。

そこで、「Dictionary」を使用する事で、非常に高速に出来ます。

また、高速ではありませんが通常の「WorksheetFunction.SumIf」のサンプルコードも記載します。

Countif、VLOOKUPやVBAの高速化は、次の記事をご覧ください。

1.SUMIF関数を高速化する方法

VBAで高速にする方法はDictionary(連想配列)オブジェクトを使用する方法です。

DictionaryのKeyに検索値となる値を格納して、重複のないリストを生成します。

リストを作成する際にItemに合計したい列の値を合計してく方法を使用します。

Keyに同じ文字列が登録されていた場合はItemに合計したい値を加算していくことで、Sumifの様な挙動を再現できます。

Dictionaryの使い方の詳細は「Excel VBA Dictionaryの使い方」をご覧ください。

2.VBAでSUMIFを高速にするサンプルコード

サンプルデータとコードでご説明します。

サンプルデータ

以下のような5万行×20万行(並びをランダム)のデータを用意しました。

「Application.WorksheetFunction.SumIf 」のループで、処理した場合は約35分かかりました。

ちなみに直接ExcelのSumIf関数で処理した場合10分くらいです。

これが、約2秒で処理されます

A列に5万行の商品名、D列にランダムに並び替えた商品名20万行とE列に価格のデータです。

上記のデータのB列にA列を検索値として、Eの価格の合計を算出します。

サンプルコード
Option Explicit

    Sub Sample1()

        Dim SearchArray As Variant
        Dim RefArray    As Variant
        Dim Keyval      As String
        Dim Itemval     As Long
        Dim MaxRowA     As Long
        Dim MaxRowD     As Long
        Dim i           As Long
        Dim n           As Long
        Dim myStr       As String
        Dim myDic       As Object
            
        MaxRowA = Cells(Rows.Count, 1).End(xlUp).Row '最終行を取得
        SearchArray = Range(Cells(2, 1), Cells(MaxRowA, 2)) '①A列と出力用にB列も配列格納
        
        MaxRowD = Cells(Rows.Count, 4).End(xlUp).Row '最終行を取得
        RefArray = Range(Cells(2, 4), Cells(MaxRowD, 5)) '②参照データとしてD~E列を格納
        
        Set myDic = CreateObject("Scripting.Dictionary")
        
        For n = 1 To UBound(RefArray) '参照用の配列を要素数分ループ
                            
            Keyval = RefArray(n, 1) '③Keyを格納
            Itemval = RefArray(n, 2) '④Itemを格納
            
            '未登録の場合登録
            '登録済みの場合はItemにE列の値を加算
            If Not myDic.Exists(Keyval) Then
            
                myDic.Add Keyval, Itemval
                
            Else
            
                myDic(Keyval) = myDic(Keyval) + Itemval
                
            End If
            
        Next n
        
        For n = 1 To UBound(SearchArray) '検索用配列の要素数分ループ
        
            Keyval = SearchArray(n, 1)
            
            SearchArray(n, 2) = myDic(Keyval) '検索値のKeyでItemを抽出
        
        Next n
        
        Range(Cells(2, 1), Cells(MaxRowA, 2)) = SearchArray '結果出力
        
        Set myDic = Nothing
    
    End Sub
コードの説明

「SearchArray = Range(Cells(2, 1), Cells(MaxRowA, 2))」で検索値と算出結果を格納するために、A列の最終行を取得してA~B列を2次元配列に格納しています。

「RefArray = Range(Cells(2, 4), Cells(MaxRowD, 5))」で参照先データを格納するためD列の最終行を取得して、D~E列を配列に格納しています。

もし合計したい列が離れたところにある場合は、その範囲まで配列に格納して、配列の列数を指定するか、新たに合計用の配列を用意すると対応できると思います。

「Set myDic = CreateObject(“Scripting.Dictionary”)」でDictionaryを使用できるようにセットします。

「For n = 1 To UBound(RefArray)」で検索範囲のデータ(配列)をループします。

「Keyval = RefArray(n, 1)」と「Itemval = RefArray(n, 2)」でそれぞれKeyとなる値と、Itemに合算する値を格納します。

KeyにD列の商品名を、Itemに価格を格納しています。

「If Not myDic.Exists(Keyval) Then」でリストにすでに登録されているか判定して、未登録の場合は「myDic.Add Keyval, Itemval」で加算する値をそのまま格納します。

すでに登録済みの場合は「myDic(Keyval) = myDic(Keyval) + Itemval」で同じKeyにItemに合算します。

続いて、検索値用のKeyで合算したDictionaryから、値を抽出します。

「For n = 1 To UBound(SearchArray)」で検索値用に格納したA~B列の配列を要素数分ループします。

「Keyval = SearchArray(n, 1)」で検索値の条件となる値(A列に該当)を格納します。

「SearchArray(n, 2) = myDic(Keyval)」で検索値のKeyでItemを抽出します。

最後に、「Range(Cells(2, 1), Cells(MaxRowA, 2)) = SearchArray」で結果を一括で出力します。

最後にセルに一括で出力しています。

3.Application.WorksheetFunction.SumIfで処理

高速ではありませんが、ExcelのSumIf関数をVBAで使用する場合のコードです。

35分かかりました。

Sub Sample2()

        Dim SearchArray As Variant
        Dim MaxRowA     As Long
        Dim MaxRowD     As Long
        Dim i           As Long
        Dim myStr       As String
        
        MaxRowA = Cells(Rows.Count, 1).End(xlUp).Row '最終行を取得
        SearchArray = Range(Cells(2, 1), Cells(MaxRowA, 2)) 'A列と出力用にB列も配列格納
        
        MaxRowD = Cells(Rows.Count, 4).End(xlUp).Row  '最終行を再定義
        
        For i = 1 To UBound(SearchArray) 'A列要素数分ループ
        
            myStr = SearchArray(i, 1) '配列の値を格納
        
            SearchArray(i, 2) = _
            Application.WorksheetFunction.SumIf _
            (Range(Cells(2, 4), Cells(MaxRowD, 4)), myStr, Range(Cells(2, 5), Cells(MaxRowD, 5)))
        
        Next i
        
        Range(Cells(2, 1), Cells(MaxRowA, 2)) = SearchArray '結果出力
        
    End Sub

4.サンプルデータダウンロード

掲載しているサンプルデータとSample1~2のVBAコードを記載したファイルです。

参照列とご自身の環境に合わせて頂くだけで使用できると思います。

6.まとめ

VLOOKUP関数やCOUNTIF関数同様に、Dictionary(連想配列)を使用した方法です。

Dictionary以外はほとんど基本的な内容ですので、サンプルコードで不明点があれば

ExcelVBA-基礎編」をご覧ください。

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