Excel VBA ツール作成サンプル 売上を自動集計する(高速編)

ExcelVBA-実用編

1記事1ツールをVBAで作るサンプルシリーズ2です。

前回のツール作成サンプルで作成したツールを高速化しました。

前回のサンプルシリーズは「ツール作成サンプル 売上を自動集計する」をご覧ください。

上記のツールでも十分使用できますが、もしデータが10万行やそれ以上となった場合には、どうしてもSumif関数や、セルへの出力を都度行っていると処理速度が遅くなってしまいます。

そこで、今回は同じサンプルツールを高速化してみたいと思います。

また、両方のツールの処理速度比較も行いたいと思います。

前回よりは難しくなりますが、少しでもイメージしやすい内容で作成します。

前回のツールの方法では「Application.ScreenUpdating = False」で描画止めても、集計部分の計測で「約27.60秒」でした。

今回のツールでは「約3.24秒」でした。(PCスペックで処理速度は変わります。)

1.ツールを作る上で大事なこと

ツールを作るには、ある程度ツールの設計を考える必要があります。

簡単なツールであれば設計を飛ばして作成しても問題ありませんが、複雑なツールになるほど設計は大事になります。

そして、ツールは1つ1つのパーツを作り、組み立てていくことで出来上がります。

また、ツール作成に慣れている場合を除いて、複雑なコードをいきなり書き始めるのではなく静的なちゃんと動くコードを書いてから、汎用性や処理速度の高いコードにしていくことをお勧めします。

2.今回使うサンプルデータ

今回は次のような注文日、注文番号、商品、単価、販売数量、売上金額の1か月分のデータ10万行を使用します。

商品の種類も5種類から100種類へ増やしました。(データ個数100001です)

3.ツールの最終アウトプットと設計(プロセス)を考える

今回も前回同様に「決まった形のファイルを読み込んで、集計をして、別ファイルに集計結果を出力する」というツールを作成します。

いきなり完成系のツールを作り始めるのではなく、まずは最終的にどのようなアウトプットを目標としているかを考えます。

そして、そのアウトプットを実現するための設計(プロセス)を考えたいと思います。

最終的なアウトプット

今回は「日別」、「商品別」の2種類の売上集計をしたいと思います。

売上と一緒に「販売数量」も一緒に集計したいと思います。

前回は日単位でしたが、10万行分データを作成したため、月単位に圧縮してみました。

全体の設計

大まかで構いませんので全体設計を考えます。

前回と同じですが、最初はこれくらいシンプルでも構いません。

全体設計の各パーツをもう少し具体的に考えてみる

ここから各パーツについてもう少し具体的に考えていきます。

今回はある程度プロセスを絞り込んで話を進めたいと思います。

ファイルを読み込む

ファイルはダイアログボックスを開いて、ユーザーに指定してもらう方法で作成します。

集計する

ここが前回と大きく変わる部分になります。

配列を使ったり、Sumif関数を使わない方法で高速化したいと思います。

プロセスイメージは次の通りです。

別ファイルに出力する

読み込み→集計の次は別ファイルに出力するパーツです。

「ユーザーに保存場所もファイル名も指定してもらう」方法を使用します。

4.コードを書く

最終的なアウトプットと設計(プロセス)が出来たので、次は各パーツのコードを書いていきます。

ファイルを読み込むコード

次のコードはダイアログを開いて、選択されたファイルを読み込みます。

ユーザーに選択させるリスクとして間違ったファイルを選択する可能性もありますので、念のため項目が形式と一致しているかの判定をしています。

ダイアログを開く方法は「ダイアログで指定したファイルを読み込む」をご覧下さい。

Sub Sample1()

    Dim FilePass    As Variant
    Dim FileName    As String
    Dim ShCount     As Long

    ShCount = ThisWorkbook.Worksheets.Count 'VBAの書かれたファイルのシート数
    
    'ダイアログを開いてファイルを指定
    FilePass = Application.GetOpenFilename("Excelブック,*.xlsx,Excelマクロ,*.xlsm,テキスト,*.txt")
    
    If FilePass <> "False" Then
        
        FileName = Dir(FilePass) 'ファイル名を取得
        
        Workbooks.Open FilePass 'ファイルを開く
        
        Worksheets(1).Copy after:=ThisWorkbook.Worksheets(ShCount) '一番最後に追加
    
        Workbooks(FileName).Close savechanges:=False
        
        ActiveSheet.Name = "データ" '読み込んだシート名を変更する
        
        'データをチェックする
        With ActiveSheet
        
            '項目が注文日/注文番号/商品/単価/販売数量/売上金額であることが条件
            If Not (.Cells(1, 1) = "注文日" And _
                .Cells(1, 2) = "注文番号" And _
                .Cells(1, 3) = "商品" And _
                .Cells(1, 4) = "単価" And _
                .Cells(1, 5) = "販売数量" And _
                .Cells(1, 6) = "売上金額") Then
                
                Application.DisplayAlerts = False
                .Delete
                Application.DisplayAlerts = True
                
                MsgBox "集計できないデータ形式です。"
                
                End
                
            End If
        
        End With
        
    Else
    
        End
        
    End If
    
End Sub

集計するコード

次のコードは読み込んだデータを集計します。

前回の日付を日単位から月単位にまとめていますが、コード内の「“yyyy/mm”」の部分を「“yyyy/mm/dd”」もしくは「“yyyy/m/d”」とすることで日単位に集計できます。

集計や装飾部分を1プロシージャにまとめて、上から順にプロセス通りに書いています。

流れとしてはアウトプット用のリストをDictionaryで生成しつつ、販売数量と売上を同時に集計します。

集計後項目を入力して、Dictionaryに格納した集計結果を配列に出力しています。

最後に一括で配列を出力してから、表を装飾しています。

リスト作成のDictionaryについては「Dictionaryの使い方」をご覧ください。

配列は「配列の使い方(基礎編)」、「配列の使い方(応用編)」をご覧ください。

より複雑なコードの場合は計算部分など、さらに「ユーザー定義関数(Functionプロシージャ)」で分割すると、複雑なツールを作る際に可読性が上がります。

Sub Sample2()

    Dim MaxRow      As Long
    Dim i           As Long
    Dim ImpWS       As Worksheet
    Dim OutWS       As Worksheet
    Dim DateDic1    As Object
    Dim DateDic2    As Object
    Dim ItemDic1    As Object
    Dim ItemDic2    As Object
    Dim myVal       As Variant
    Dim DateKey     As Variant
    Dim ItemKey     As Variant
    
    Const title1    As String = "日別売上"
    Const title2    As String = "商品別売上"
    
    Application.ScreenUpdating = False
    
    Set DateDic1 = CreateObject("Scripting.Dictionary") 'Dictionaryをセット
    Set DateDic2 = CreateObject("Scripting.Dictionary") 'Dictionaryをセット
    Set ItemDic1 = CreateObject("Scripting.Dictionary") '販売数量Dictionaryをセット
    Set ItemDic2 = CreateObject("Scripting.Dictionary") '売上Dictionaryをセット
    
    Set ImpWS = Worksheets("データ")
    
    Worksheets.Add after:=Worksheets(Worksheets.Count)
    ActiveSheet.Name = "集計結果"
    Set OutWS = Worksheets("集計結果")
    
    With ImpWS
        
        MaxRow = .Cells(Rows.Count, 1).End(xlUp).Row
        
        '■■日付のリストと販売数量と売上集計■■
        For i = 2 To MaxRow
            
            '表示形式を"yyyy/mm/dd"とすることで日単位で集計できます。
            myVal = Format(.Cells(i, 1), "yyyy/mm")
        
            If Not DateDic1.exists(myVal) Then
    
                DateDic1.Add myVal, .Cells(i, 5) '販売数量
                DateDic2.Add myVal, .Cells(i, 6) '売上
            Else
            
                DateDic1(myVal) = DateDic1(myVal) + .Cells(i, 5) '販売数量
                DateDic2(myVal) = DateDic2(myVal) + .Cells(i, 6) '売上
            End If
        
        Next i
        
        DateKey = DateDic1.keys
        
        '■■商品のリストと販売数量と売上集計■■
        For i = 2 To MaxRow
            
            myVal = .Cells(i, 3)
        
            If Not ItemDic1.exists(myVal) Then
    
                ItemDic1.Add myVal, .Cells(i, 5) '販売数量
                ItemDic2.Add myVal, .Cells(i, 5) '売上
            Else
            
                ItemDic1(myVal) = ItemDic1(myVal) + .Cells(i, 5) '販売数量
                ItemDic2(myVal) = ItemDic2(myVal) + .Cells(i, 6) '売上
            
            End If
        
        Next i
        
        ItemKey = ItemDic1.keys
           
    End With
    
    With OutWS
    
        '■■アウトプット作成■■
        .Cells(2, 2) = title1
        .Cells(3, 2) = ImpWS.Cells(1, 1)
        .Cells(3, 3) = ImpWS.Cells(1, 5)
        .Cells(3, 4) = ImpWS.Cells(1, 6)
        .Cells(2, 6) = title2
        .Cells(3, 6) = ImpWS.Cells(1, 3)
        .Cells(3, 7) = ImpWS.Cells(1, 5)
        .Cells(3, 8) = ImpWS.Cells(1, 6)
        
        
        '■■アウトプット用配列に格納後出力■■
        ReDim myArray(0 To UBound(DateKey), 0 To 2)
    
        For i = 0 To UBound(DateKey)
        
            myArray(i, 0) = DateKey(i)
            myArray(i, 1) = DateDic1(DateKey(i))
            myArray(i, 2) = DateDic2(DateKey(i))
        
        Next i
        
        Range(.Cells(4, 2), .Cells(UBound(DateKey) + 4, 4)) = myArray
        
        .Cells(i + 4, 2) = "総計"
        
        '■■アウトプット用配列に格納後出力■■
        ReDim myArray(0 To UBound(ItemKey), 0 To 2)
    
        For i = 0 To UBound(ItemKey)
        
            myArray(i, 0) = ItemKey(i)
            myArray(i, 1) = ItemDic1(ItemKey(i))
            myArray(i, 2) = ItemDic2(ItemKey(i))
        
        Next i
        
        Range(.Cells(4, 6), .Cells(UBound(ItemKey) + 4, 8)) = myArray
                
        .Cells(i + 4, 6) = "総計"
        
        '■■合計集計する■■
        MaxRow = .Cells(Rows.Count, 2).End(xlUp).Row
        
        .Cells(MaxRow, 3) = Application.WorksheetFunction.Sum(Range(.Cells(4, 3), .Cells(MaxRow - 1, 3)))
        .Cells(MaxRow, 4) = Application.WorksheetFunction.Sum(Range(.Cells(4, 4), .Cells(MaxRow - 1, 4)))
        
        '■■装飾する■■
        Range(.Cells(4, 2), .Cells(MaxRow, 2)).NumberFormatLocal = "yyyy/mm" '日付を月単位に指定
        Range(.Cells(3, 2), .Cells(MaxRow, 4)).Borders.LineStyle = xlContinuous '罫線
        Range(.Cells(3, 2), .Cells(3, 4)).Interior.Color = RGB(64, 64, 64) '背景色
        Range(.Cells(3, 2), .Cells(3, 4)).Font.Color = RGB(255, 255, 255) '文字色
        Range(.Cells(3, 2), .Cells(3, 4)).HorizontalAlignment = xlCenter '中央揃え
        
        '■■合計集計する■■
        MaxRow = .Cells(Rows.Count, 6).End(xlUp).Row
        
        .Cells(MaxRow, 7) = Application.WorksheetFunction.Sum(Range(.Cells(4, 7), .Cells(MaxRow - 1, 7)))
        .Cells(MaxRow, 8) = Application.WorksheetFunction.Sum(Range(.Cells(4, 8), .Cells(MaxRow - 1, 8)))
        
        '■■装飾する■■
        Range(.Cells(3, 6), .Cells(MaxRow, 8)).Borders.LineStyle = xlContinuous  '罫線
        Range(.Cells(3, 6), .Cells(3, 8)).Interior.Color = RGB(64, 64, 64)  '背景色
        Range(.Cells(3, 6), .Cells(3, 8)).Font.Color = RGB(255, 255, 255)  '文字色
        Range(.Cells(3, 6), .Cells(3, 8)).HorizontalAlignment = xlCenter   '中央揃え
        
        .Columns.AutoFit
        
    End With
    
    Application.ScreenUpdating = True

End Sub

集計結果を別ファイルに出力するコード

集計結果が出来たら、ダイアログを開いてファイル名を指定してもらうと、別ファイルとして保存されます。

予めダイアログに「集計結果」というファイル名を入力しています。

Sub Sample3()
    
    Dim ImpWS       As Worksheet
    Dim OutWS       As Worksheet
    Dim FileName    As String
    
    Set ImpWS = Worksheets("データ")
    Set OutWS = Worksheets("集計結果")
    
    OutWS.Move
    
    FileName = Application.GetSaveAsFilename("集計結果", _
    "Excel2013,*.xlsx,Excel2003,*.xls,Excel.csv,*.csv", , "Excelブックを保存")
    
    If StrConv(FileName, vbUpperCase) <> "FALSE" Then
    
        On Error Resume Next
        ActiveWorkbook.SaveAs FileName:=FileName
        ActiveWorkbook.Close
        
    End If
    
    ThisWorkbook.Activate
    Application.DisplayAlerts = False
    ImpWS.Delete
    Application.DisplayAlerts = True
    
End Sub

マクロを呼び出すボタンを作成

VBEからコードを呼び出すのは不便なので、オートシェイプで図を挿入し、マクロの登録をすることでボタンを押すと上記で作成したコードが呼び出されるようにしました。

Sub MAIN_Click()

    Call Sample1
    Call Sample2
    Call Sample3

End Sub

5.サンプルファイルダウンロード

今回使用したツールのファイルと読み込む用に作成したサンプルファイルをこちらからダウンロードできます。

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