【VBA】売上データを月別・担当者別に自動集計する方法(コピペOK)

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

この記事でわかること

  • 売上明細データから月別の売上合計をDictionaryで自動集計できる
  • 月別×担当者別のクロス集計表を自動生成できる
  • グラフの自動作成・前月比計算・集計シートの自動生成まで実務で使える形に仕上がる

対象: Excel 2016以降 / Microsoft 365、Windows 10/11

どんな場面で使う?

  • 売上明細データから月別の売上推移表を自動で作りたいとき
  • 担当者別・商品別・部門別のクロス集計表をワンクリックで生成したいとき
  • 前月比や達成率を自動計算して、売上の変動に即座に気づけるようにしたいとき
  • 毎月の売上報告資料にグラフ付き集計表を自動で添付したいとき

完成イメージ(Before / After)

Before(手作業):

  1. 売上明細を月ごとにフィルタで絞り込み
  2. SUMIFS関数で担当者ごとの合計を手入力
  3. 月が変わるたびに関数を追加、グラフも手動で更新

After(VBAで自動化):

  1. ボタンを押す(またはAlt+F8で実行)
  2. 売上明細から月別×担当者別のクロス集計表を自動生成
  3. グラフ作成+前月比計算まで全自動

所要時間: コピペ → 実行まで約10分

自分も以前、毎月の売上報告のたびにExcelのフィルタとSUMIFSで1時間かけて集計表を作っていた。担当者が10人×12ヶ月分ともなると、集計漏れや数式の参照ミスが地味にストレスだった。VBAで自動化してからは、ボタン1つで集計表が完成するようになって、報告書作成が5分で終わるようになった。この記事で、同じように売上集計に時間を取られている人がサクッと自動化できればうれしい。

売上明細データからDictionaryで月別・担当者別のクロス集計表を自動生成する。

Dictionaryを使った集計の基本は Dictionaryで重複チェック・集計を高速化する方法 で解説している。Dictionaryに馴染みがない人は先にそちらを読むとスムーズに進められる。

実行前の準備

バックアップを取る

実務版コードは集計シートを新規作成または上書きする。必ずファイルのコピーを別フォルダに保存してから実行する。

Excelをマクロ有効ブック(.xlsm)で保存する

拡張子が .xlsx のままだとマクロが保存できない。

  1. 「ファイル」→「名前を付けて保存」
  2. ファイルの種類を「Excelマクロ有効ブック (*.xlsm)」に変更
  3. 保存

シート構成

シート1: 売上明細(シート名: “売上明細”)

A列(日付) B列(担当者) C列(商品名) D列(数量) E列(単価) F列(金額)
2026/01/05 田中 コピー用紙 A4 10 500 5000
2026/01/12 鈴木 ボールペン 黒 50 100 5000
2026/02/03 田中 クリアファイル 30 150 4500
2026/02/15 佐藤 付箋 75mm 20 200 4000
2026/03/01 鈴木 コピー用紙 A4 15 500 7500
2026/03/10 田中 ボールペン 黒 100 100 10000
  • 1行目はヘッダー
  • A列は日付型(yyyy/mm/dd形式)
  • F列「金額」が集計対象

手順(コピペ → 実行まで約10分)

VBE(コードを書く画面)を開く

  1. Excelで Alt + F11 を押す

標準モジュールを挿入する

  1. VBEのメニュー →「挿入」→「標準モジュール」

コードを貼り付けて実行する

  1. コードウィンドウに、下のコードをそのままコピペする
  2. Alt + F8 → マクロ名を選んで「実行」

ボタンに割り当てれば毎回Alt+F8を押さなくて済む。方法は マクロをボタン1つで実行する方法 を参照。

コード(最小版)– 月別売上をDictionaryで集計してメッセージ表示

まずは売上明細から月別の売上合計を集計し、MsgBoxで一覧表示するだけの最小版。シートへの書き込みはしないので安全に動作確認できる。


'============================================================
' ■ 月別売上集計(最小版)
'   → 売上明細シートから月別の売上合計をDictionaryで集計
'   → 結果をMsgBoxで表示(シートへの書き込みなし)
'   ※ 日付列が日付型でない場合はエラーになる。
'     実務版ではIsDate()でガード済み。
'============================================================
Sub CalcMonthlySalesSimple()

    Dim wsData As Worksheet
    Set wsData = Worksheets("売上明細")

    '--- 最終行を取得
    Dim lastRow As Long
    lastRow = wsData.Cells(wsData.Rows.Count, 1).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox "売上明細データがありません。", vbExclamation
        Exit Sub
    End If

    '--- Dictionaryで月別に集計
    '   Key = "yyyy/mm" 形式の月キー
    '   Item = 売上合計
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    Dim r As Long
    For r = 2 To lastRow

        Dim cellDate As Variant
        cellDate = wsData.Cells(r, 1).Value    '日付(A列)

        If Not IsDate(cellDate) Then GoTo NextRow

        '月キーを生成(yyyy/mm形式)
        Dim monthKey As String
        monthKey = Format(CDate(cellDate), "yyyy/mm")

        '金額を取得(F列)
        Dim amount As Double
        If IsNumeric(wsData.Cells(r, 6).Value) Then
            amount = CDbl(wsData.Cells(r, 6).Value)
        Else
            amount = 0
        End If

        '集計
        If dict.Exists(monthKey) Then
            dict(monthKey) = dict(monthKey) + amount
        Else
            dict.Add monthKey, amount
        End If

NextRow:
    Next r

    '--- 結果表示
    If dict.Count = 0 Then
        MsgBox "集計対象のデータがありません。", vbInformation
        Exit Sub
    End If

    Dim msg As String
    Dim k As Variant
    For Each k In dict.Keys
        msg = msg & k & ": " & Format(dict(k), "#,##0") & " 円" & vbCrLf
    Next k

    MsgBox "月別売上集計(" & dict.Count & " ヶ月分)" & vbCrLf & vbCrLf & msg, vbInformation

End Sub

書き換えポイント

変数 説明 初期値
Worksheets("売上明細") 売上明細データのシート名 "売上明細"
Cells(r, 1) 日付の列番号 1(A列)
Cells(r, 6) 金額の列番号 6(F列)

コード(実務版)– 月別×担当者別クロス集計+グラフ自動作成+前月比計算

この仕組みを作ってからは、月次の売上報告書の準備が1時間→5分に短縮された。上司から「先月比で売上どうだった?」と聞かれても、ボタンを押してすぐ回答できるようになった。

※ 集計シートの前回データは上書きされます。実行前にバックアップを取ってください。


'============================================================
' ■ 売上クロス集計表 自動生成(実務版)
'   → 売上明細から月別×担当者別のクロス集計表を自動生成
'   → 前月比を自動計算
'   → 集計結果から棒グラフを自動作成
'   → 集計シートを自動生成(なければ新規作成)
'
' シート構成:
'   "売上明細"  : A=日付, B=担当者, C=商品名, D=数量, E=単価, F=金額
'   "売上集計"  : マクロが自動生成
'============================================================
Sub CreateSalesSummary()

    On Error GoTo ErrHandler

    Application.ScreenUpdating = False

    '--- ★書き換えポイント ---
    Const DATA_SHEET As String = "売上明細"
    Const SUMMARY_SHEET As String = "売上集計"

    '売上明細の列番号
    Const COL_DATE As Long = 1       'A列: 日付
    Const COL_PERSON As Long = 2     'B列: 担当者
    Const COL_AMOUNT As Long = 6     'F列: 金額

    '集計表のヘッダー開始行
    Const HEADER_ROW As Long = 4
    '--- ★ここまで ---

    Dim wsData As Worksheet
    Set wsData = Worksheets(DATA_SHEET)

    '--- 売上明細の最終行を取得
    Dim lastRow As Long
    lastRow = wsData.Cells(wsData.Rows.Count, COL_DATE).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox "売上明細データがありません。", vbExclamation
        GoTo CleanUp
    End If

    '--- ステップ1: 月リスト・担当者リストを収集
    Dim dictMonths As Object
    Set dictMonths = CreateObject("Scripting.Dictionary")

    Dim dictPersons As Object
    Set dictPersons = CreateObject("Scripting.Dictionary")

    '--- ステップ2: クロス集計用Dictionary
    '   Key = "yyyy/mm|担当者名"
    '   Item = 売上合計
    Dim dictCross As Object
    Set dictCross = CreateObject("Scripting.Dictionary")

    '--- 月別合計用Dictionary
    Dim dictMonthTotal As Object
    Set dictMonthTotal = CreateObject("Scripting.Dictionary")

    Dim r As Long
    For r = 2 To lastRow

        Dim cellDate As Variant
        cellDate = wsData.Cells(r, COL_DATE).Value

        If Not IsDate(cellDate) Then GoTo NextDataRow

        Dim monthKey As String
        monthKey = Format(CDate(cellDate), "yyyy/mm")

        Dim person As String
        person = CStr(wsData.Cells(r, COL_PERSON).Value)

        If person = "" Then GoTo NextDataRow

        Dim amount As Double
        If IsNumeric(wsData.Cells(r, COL_AMOUNT).Value) Then
            amount = CDbl(wsData.Cells(r, COL_AMOUNT).Value)
        Else
            amount = 0
        End If

        '月リスト・担当者リストに登録
        If Not dictMonths.Exists(monthKey) Then dictMonths.Add monthKey, 0
        If Not dictPersons.Exists(person) Then dictPersons.Add person, 0

        'クロス集計
        Dim crossKey As String
        crossKey = monthKey & "|" & person

        If dictCross.Exists(crossKey) Then
            dictCross(crossKey) = dictCross(crossKey) + amount
        Else
            dictCross.Add crossKey, amount
        End If

        '月別合計
        If dictMonthTotal.Exists(monthKey) Then
            dictMonthTotal(monthKey) = dictMonthTotal(monthKey) + amount
        Else
            dictMonthTotal.Add monthKey, amount
        End If

NextDataRow:
    Next r

    If dictMonths.Count = 0 Then
        MsgBox "集計対象のデータがありません。", vbInformation
        GoTo CleanUp
    End If

    '--- 月キーをソート(昇順)
    Dim monthKeys() As String
    ReDim monthKeys(dictMonths.Count - 1)

    Dim idx As Long
    idx = 0
    Dim k As Variant
    For Each k In dictMonths.Keys
        monthKeys(idx) = CStr(k)
        idx = idx + 1
    Next k

    '単純バブルソート(月数は多くても数十なので十分)
    Dim i As Long, j As Long
    For i = 0 To UBound(monthKeys) - 1
        For j = i + 1 To UBound(monthKeys)
            If monthKeys(i) > monthKeys(j) Then
                Dim swp As String
                swp = monthKeys(i)
                monthKeys(i) = monthKeys(j)
                monthKeys(j) = swp
            End If
        Next j
    Next i

    '--- 担当者リストを配列に変換
    Dim personKeys() As String
    ReDim personKeys(dictPersons.Count - 1)
    idx = 0
    For Each k In dictPersons.Keys
        personKeys(idx) = CStr(k)
        idx = idx + 1
    Next k

    '担当者名もソート
    For i = 0 To UBound(personKeys) - 1
        For j = i + 1 To UBound(personKeys)
            If personKeys(i) > personKeys(j) Then
                swp = personKeys(i)
                personKeys(i) = personKeys(j)
                personKeys(j) = swp
            End If
        Next j
    Next i

    '--- 集計シートを準備(なければ新規作成)
    Dim wsSummary As Worksheet
    Dim sheetExists As Boolean
    sheetExists = False

    Dim ws As Worksheet
    For Each ws In ThisWorkbook.Worksheets
        If ws.Name = SUMMARY_SHEET Then
            sheetExists = True
            Exit For
        End If
    Next ws

    If sheetExists Then
        Set wsSummary = Worksheets(SUMMARY_SHEET)
        wsSummary.Cells.ClearContents
        wsSummary.Cells.Interior.ColorIndex = xlNone

        '既存グラフを削除
        Dim co As ChartObject
        For Each co In wsSummary.ChartObjects
            co.Delete
        Next co
    Else
        Set wsSummary = Worksheets.Add(After:=Worksheets(Worksheets.Count))
        wsSummary.Name = SUMMARY_SHEET
    End If

    '--- レポートヘッダー
    wsSummary.Range("A1").Value = "売上集計表(自動生成)"
    wsSummary.Range("A1").Font.Bold = True
    wsSummary.Range("A1").Font.Size = 14

    wsSummary.Range("A2").Value = "集計日: " & Format(Date, "yyyy/mm/dd")
    wsSummary.Range("C2").Value = "対象期間: " & monthKeys(0) & " 〜 " & monthKeys(UBound(monthKeys))
    wsSummary.Range("F2").Value = "担当者数: " & dictPersons.Count & " 名"

    '--- テーブルヘッダー
    '   A列=月, B列〜=各担当者, 最後=合計, その次=前月比
    wsSummary.Cells(HEADER_ROW, 1).Value = "月"

    Dim col As Long
    For col = 0 To UBound(personKeys)
        wsSummary.Cells(HEADER_ROW, col + 2).Value = personKeys(col)
    Next col

    Dim totalCol As Long
    totalCol = UBound(personKeys) + 3          '合計列
    wsSummary.Cells(HEADER_ROW, totalCol).Value = "合計"

    Dim momCol As Long
    momCol = totalCol + 1                       '前月比列
    wsSummary.Cells(HEADER_ROW, momCol).Value = "前月比"

    '--- ヘッダー行の書式
    With wsSummary.Range(wsSummary.Cells(HEADER_ROW, 1), _
                         wsSummary.Cells(HEADER_ROW, momCol))
        .Font.Bold = True
        .Interior.Color = RGB(68, 114, 196)
        .Font.Color = RGB(255, 255, 255)
    End With

    '--- データ書き込み
    Dim writeRow As Long
    writeRow = HEADER_ROW + 1

    Dim prevTotal As Double
    prevTotal = 0

    Dim mi As Long
    For mi = 0 To UBound(monthKeys)

        wsSummary.Cells(writeRow, 1).Value = monthKeys(mi)

        Dim rowTotal As Double
        rowTotal = 0

        Dim pi As Long
        For pi = 0 To UBound(personKeys)

            crossKey = monthKeys(mi) & "|" & personKeys(pi)

            If dictCross.Exists(crossKey) Then
                wsSummary.Cells(writeRow, pi + 2).Value = dictCross(crossKey)
                wsSummary.Cells(writeRow, pi + 2).NumberFormat = "#,##0"
                rowTotal = rowTotal + dictCross(crossKey)
            Else
                wsSummary.Cells(writeRow, pi + 2).Value = 0
            End If

        Next pi

        '合計列
        wsSummary.Cells(writeRow, totalCol).Value = rowTotal
        wsSummary.Cells(writeRow, totalCol).NumberFormat = "#,##0"

        '前月比列(2ヶ月目以降)
        If mi = 0 Then
            wsSummary.Cells(writeRow, momCol).Value = "-"
        Else
            If prevTotal = 0 Then
                wsSummary.Cells(writeRow, momCol).Value = "-"
            Else
                Dim momRatio As Double
                momRatio = rowTotal / prevTotal
                wsSummary.Cells(writeRow, momCol).Value = momRatio
                wsSummary.Cells(writeRow, momCol).NumberFormat = "0.0%"

                '前月比が100%未満なら赤文字
                If momRatio < 1 Then
                    wsSummary.Cells(writeRow, momCol).Font.Color = RGB(192, 0, 0)
                Else
                    wsSummary.Cells(writeRow, momCol).Font.Color = RGB(0, 128, 0)
                End If
            End If
        End If

        prevTotal = rowTotal
        writeRow = writeRow + 1

    Next mi

    '--- 合計行を追加
    Dim sumRow As Long
    sumRow = writeRow
    wsSummary.Cells(sumRow, 1).Value = "合計"
    wsSummary.Cells(sumRow, 1).Font.Bold = True

    For pi = 0 To UBound(personKeys)
        Dim personSum As Double
        personSum = 0
        Dim sumKey As String
        For mi = 0 To UBound(monthKeys)
            sumKey = monthKeys(mi) & "|" & personKeys(pi)
            If dictCross.Exists(sumKey) Then
                personSum = personSum + dictCross(sumKey)
            End If
        Next mi
        wsSummary.Cells(sumRow, pi + 2).Value = personSum
        wsSummary.Cells(sumRow, pi + 2).NumberFormat = "#,##0"
        wsSummary.Cells(sumRow, pi + 2).Font.Bold = True
    Next pi

    '全体合計
    Dim grandTotal As Double
    grandTotal = 0
    For mi = 0 To UBound(monthKeys)
        If dictMonthTotal.Exists(monthKeys(mi)) Then
            grandTotal = grandTotal + dictMonthTotal(monthKeys(mi))
        End If
    Next mi
    wsSummary.Cells(sumRow, totalCol).Value = grandTotal
    wsSummary.Cells(sumRow, totalCol).NumberFormat = "#,##0"
    wsSummary.Cells(sumRow, totalCol).Font.Bold = True

    '--- 罫線を設定
    Dim dataLastRow As Long
    dataLastRow = sumRow
    With wsSummary.Range(wsSummary.Cells(HEADER_ROW, 1), _
                         wsSummary.Cells(dataLastRow, momCol))
        .Borders.LineStyle = xlContinuous
        .Borders.Weight = xlThin
    End With

    '--- グラフ自動作成(月別合計の棒グラフ)
    Dim chartDataRows As Long
    chartDataRows = UBound(monthKeys) + 1   '月数

    '月ラベル範囲: A5:A(5+月数-1)
    '合計値範囲: totalCol列の5行目〜
    Dim chartTop As Double
    chartTop = wsSummary.Cells(dataLastRow + 2, 1).Top

    Dim cht As ChartObject
    Set cht = wsSummary.ChartObjects.Add( _
        Left:=wsSummary.Cells(dataLastRow + 2, 1).Left, _
        Top:=chartTop, _
        Width:=500, _
        Height:=300)

    With cht.Chart
        .ChartType = xlColumnClustered

        '系列を手動で設定
        .SeriesCollection.NewSeries
        .SeriesCollection(1).Name = "月別売上合計"

        'カテゴリ(月ラベル)
        .SeriesCollection(1).XValues = _
            wsSummary.Range(wsSummary.Cells(HEADER_ROW + 1, 1), _
                            wsSummary.Cells(HEADER_ROW + chartDataRows, 1))

        '値(合計列)
        .SeriesCollection(1).Values = _
            wsSummary.Range(wsSummary.Cells(HEADER_ROW + 1, totalCol), _
                            wsSummary.Cells(HEADER_ROW + chartDataRows, totalCol))

        .HasTitle = True
        .ChartTitle.Text = "月別売上推移"
        .HasLegend = False

        '数値軸のフォーマット
        .Axes(xlValue).TickLabels.NumberFormat = "#,##0"
    End With

    '--- 列幅を自動調整
    wsSummary.Columns(1).Resize(, momCol).AutoFit

    '--- 集計シートをアクティブにする
    wsSummary.Activate

    MsgBox "売上集計表を生成しました。" & vbCrLf & _
           "対象期間: " & monthKeys(0) & " 〜 " & monthKeys(UBound(monthKeys)) & vbCrLf & _
           "担当者数: " & dictPersons.Count & " 名" & vbCrLf & _
           "月数: " & dictMonths.Count & " ヶ月" & vbCrLf & _
           "売上合計: " & Format(grandTotal, "#,##0") & " 円", vbInformation

CleanUp:
    Application.ScreenUpdating = True
    Exit Sub

ErrHandler:
    Application.ScreenUpdating = True
    MsgBox "エラーが発生しました。" & vbCrLf & _
           "エラー番号: " & Err.Number & vbCrLf & _
           "内容: " & Err.Description, vbCritical
End Sub

書き換えポイント

定数 説明 初期値
DATA_SHEET 売上明細データのシート名 "売上明細"
SUMMARY_SHEET 集計シートの出力名 "売上集計"
COL_DATE 日付の列番号 1(A列)
COL_PERSON 担当者の列番号 2(B列)
COL_AMOUNT 金額の列番号 6(F列)
HEADER_ROW 集計表のヘッダー開始行 4

ピボットテーブルでも同様の集計ができる。VBA操作の違いは ピボットテーブルをVBAで自動生成する方法 を参照。グラフ作成の細かい設定は データ範囲からグラフを自動作成する方法 でも解説している。

エラー処理の詳細は エラー処理(On Error)で止まらないマクロを作る方法 を参照。

よくある落とし穴5選

1. 日付列が文字列型で月キーの生成に失敗する

自分もこれで30分溶かした。CSVから取り込んだ売上データで、日付列が「2026/01/05」という文字列になっていてFormat()がエラーを返した。IsDate()で事前チェックし、文字列の場合はCDate()で変換してから使う。実務版コードには対策を入れてある。

# 症状 原因 対策
1 集計結果が0件になる 日付列が文字列型でIsDate()がFalseを返す CDate()で日付変換。CSVインポート時に日付列の書式を確認

2. 担当者名の表記ゆれで別人として集計される

# 症状 原因 対策
2 同じ担当者が2列に分かれる “田中”と”田中 “(末尾スペース)が別キーになる Trim()で前後の空白を除去してからDictionaryに登録

3. 金額列に空白やエラー値があると集計がずれる

# 症状 原因 対策
3 合計金額が期待値と異なる 空白セルやエラー値がIsNumericでFalseになり0扱い 元データで空白行やエラーセルがないか事前に確認する

4. グラフの系列データ範囲がずれて空のグラフになる

# 症状 原因 対策
4 グラフが空白で表示される 集計データが1行もないときにグラフ生成を実行した データ件数チェック後にグラフ生成する。0件時はスキップ

5. 月数が多すぎて集計表が横に長くなり見づらい

# 症状 原因 対策
5 集計表が画面に収まらない 年度をまたいだ大量データを一度に集計した 期間を絞って実行するか、年度ごとにシートを分けて集計する

VBAで売上集計の結果が0件になるときの対処法

「マクロを実行したのに集計表が空」という場合、日付列がテキスト形式で格納されていてFormat()による月キー生成に失敗している可能性が高い。IsDate()でチェックし、文字列ならCDate()で日付型に変換してから集計しよう。CSVインポート後は日付列の書式を必ず確認すること。

VBAで担当者別集計が重複するときの対処法

「同じ担当者が2列に分かれて集計される」場合、担当者名に余分なスペースが含まれていて別キーとして扱われている。Trim()で前後の空白を除去してからDictionaryのキーに登録することで解決できる。全角スペースも混在しているならReplace()で全角スペースも除去しよう。

FAQ

Q1: 集計軸を「商品別」に変えたい

COL_PERSON(担当者列)を商品名の列番号に変更するだけで、月別×商品別のクロス集計表に切り替わる。たとえば商品名がC列なら Const COL_PERSON As Long = 3 に変更する。

Q2: 四半期ごとに集計したい

月キーの生成部分を変更する。Format(CDate(cellDate), "yyyy/mm") を以下のように書き換える。


Dim qtr As Long
qtr = (Month(CDate(cellDate)) - 1) \ 3 + 1
monthKey = Format(Year(CDate(cellDate)), "0000") & "/Q" & qtr

Q3: 前月比ではなく前年同月比を出したい

前年同月のキーを生成して比較する。たとえば「2026/03」の前年同月は「2025/03」。dictMonthTotalから前年キーの値を取得して割り算する。日付操作の詳細は 2つの日付の差分・加算・比較を計算する方法 を参照。

Q4: グラフの種類を折れ線グラフに変えたい

.ChartType = xlColumnClustered.ChartType = xlLine に変更する。積み上げ棒グラフなら xlColumnStacked。グラフ種類の詳細は データ範囲からグラフを自動作成する方法 を参照。

Q5: 集計結果をPDFで出力したい

集計シートのアクティブ化の前に以下を追加する。


wsSummary.ExportAsFixedFormat Type:=xlTypePDF, _
    Filename:=ThisWorkbook.Path & "\売上集計_" & Format(Date, "yyyymmdd") & ".pdf"

PDF出力の詳細は ExcelファイルをPDFに一括変換する方法 を参照。

まとめ

  • 売上明細データからDictionaryで月別×担当者別のクロス集計表を1クリックで自動生成できるようになった
  • 最小版で動作確認 → 実務版でグラフ作成・前月比計算まで自動化、の2段階で進められる
  • 集計軸は担当者だけでなく商品別・部門別にも簡単に切り替えられる
  • 前月比が100%未満の月は赤文字で表示されるので、売上推移の変化に気づきやすい

関連記事

次にやりたくなること

コメント

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