Excel VBA 逆引き集 | 実務テンプレ完全版(超再利用部品) – ノーコード集計ツール

Excel VBA
スポンサーリンク

ねらい:集計ロジックは「設定シート」に閉じ込めて、コードは一切いじらない

毎回「この列でグループ化して、この列を合計して…」という集計マクロを書き換えていくと、
集計パターンが増えるたびにコードが増え、誰も触れないマクロが量産されます。

ノーコード集計ツールのゴールはシンプルです。
どのシートを集計対象にするか、どの列でグループ化するか、どの列を合計・件数・最大値などにするか。
これをすべて「設定シート」に書くだけで、VBAのコードは一切触らずに集計が走るようにすることです。

コードは「汎用集計エンジン」として一度だけ作る。
集計内容は「設定シート」で差し替える。
この分離ができると、現場の人が自分で集計パターンを増やせるようになります。


全体設計:集計設定シート+汎用集計エンジン

シート構成のイメージ

最低限、次の三つを用意します。

集計対象データを置くシート(例:Data)
売上明細、アクセスログ、アンケート結果など、行明細が並ぶシートです。

集計結果を出力するシート(例:Agg)
グループごとの合計や件数などを出すシートです。

集計設定シート(例:ConfigAgg)
どのシートを、どの列で、どう集計するかを表形式で書くシートです。

VBA側は「ConfigAgg を読み、そこに書かれたルール通りに Data を集計して Agg に出す」汎用エンジンだけを持ちます。
集計内容を変えたいときは、ConfigAgg の内容だけを変えます。


集計設定シートの設計:何をどう書くか

1行=1つの集計ルールという形にする

ConfigAgg シートに、次のような列を用意します。

A列:有効フラグ(Y の行だけ処理)
B列:SourceSheet(集計元シート名)
C列:TargetSheet(集計結果シート名)
D列:GroupCols(グループ化に使う列。カンマ区切り)
E列:ValueCol(集計対象の値列)
F列:AggFunc(集計関数名:SUM, COUNT, MAX, MIN など)
G列:OutStartCol(出力開始列)

イメージを表にするとこうなります。

ABCDEFG
YDataAggA,BESUMA
YDataAgg2BECOUNTA

1行目は「Data シートを、A列とB列でグループ化し、E列を合計して Agg シートに出す」という意味です。
2行目は「Data シートを、B列でグループ化し、E列の件数を数えて Agg2 シートに出す」という意味です。

ここまで決めてしまえば、「どんな集計をしたいか」はすべて表で表現できます。
VBAは、この表を読み取って動くだけです。


コア部品:行明細を「キー→集計値」にまとめる汎用関数

グループキーを文字列として組み立てる

複数列でグループ化するために、「行の中からグループキーを作る」関数を用意します。

' ModAggEngine.bas
Option Explicit

Private Function BuildGroupKey(ByVal rowData As Variant, ByVal groupCols As Variant) As String
    Dim i As Long
    Dim parts() As String
    ReDim parts(1 To UBound(groupCols) + 1)
    
    For i = LBound(groupCols) To UBound(groupCols)
        parts(i + 1) = CStr(rowData(1, CLng(groupCols(i))))
    Next
    
    BuildGroupKey = Join(parts, "||")
End Function
VB

groupCols は「{1,2}」のような列番号配列を想定しています。
キーは「値1||値2」のような文字列になります(区切り文字は被らないものにしておく)。

集計用の構造体と Dictionary

集計値を持つための構造体を定義します。

Private Type AggValue
    Sum As Double
    Count As Long
    Max As Double
    Min As Double
    First As Variant
End Type
VB

そして、「キー→AggValue」を持つ Dictionary を使って集計します。


集計設定を読み込む:ConfigAgg → ルール配列

集計ルール1行分の型を定義する

Private Type AggRule
    Enabled As Boolean
    SourceSheet As String
    TargetSheet As String
    GroupCols As Variant
    ValueCol As Long
    AggFunc As String
    OutStartCol As Long
End Type
VB

列番号は数字で扱いたいので、列記号(A,B,C…)でも番号に変換できるようにします。

Private Function ColToNumber(ByVal colRef As Variant) As Long
    If IsNumeric(colRef) Then
        ColToNumber = CLng(colRef)
    Else
        ColToNumber = Range(CStr(colRef) & "1").Column
    End If
End Function
VB

グループ列の「A,B」のような文字列を列番号配列に変換する関数も用意します。

Private Function ParseGroupCols(ByVal s As String) As Variant
    Dim parts As Variant
    parts = Split(s, ",")
    
    Dim cols() As Long
    ReDim cols(0 To UBound(parts))
    
    Dim i As Long
    For i = 0 To UBound(parts)
        cols(i) = ColToNumber(Trim$(parts(i)))
    Next
    
    ParseGroupCols = cols
End Function
VB

ConfigAgg から AggRule 配列を作る

Private Function LoadAggRules() As Variant
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets("ConfigAgg")
    
    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    If lastRow < 2 Then
        LoadAggRules = Empty
        Exit Function
    End If
    
    Dim data As Variant
    data = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 7)).Value
    
    Dim rules() As AggRule
    ReDim rules(1 To UBound(data, 1))
    
    Dim i As Long
    For i = 1 To UBound(data, 1)
        rules(i).Enabled = (UCase$(CStr(data(i, 1))) = "Y")
        rules(i).SourceSheet = CStr(data(i, 2))
        rules(i).TargetSheet = CStr(data(i, 3))
        rules(i).GroupCols = ParseGroupCols(CStr(data(i, 4)))
        rules(i).ValueCol = ColToNumber(data(i, 5))
        rules(i).AggFunc = UCase$(CStr(data(i, 6)))
        rules(i).OutStartCol = ColToNumber(data(i, 7))
    Next
    
    LoadAggRules = rules
End Function
VB

これで、「ConfigAgg の内容」が AggRule の配列としてメモリに載ります。


汎用集計エンジン:設定に従って集計を実行する

速度アップ用のラッパー

Private Sub SpeedOn()
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
End Sub

Private Sub SpeedOff()
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.ScreenUpdating = True
End Sub
VB

メイン処理:ルールごとに集計をかける

Public Sub RunAggTool()
    Dim rules As Variant
    rules = LoadAggRules()
    If IsEmpty(rules) Then
        MsgBox "ConfigAggに集計設定がありません。", vbInformation
        Exit Sub
    End If
    
    SpeedOn
    
    Dim i As Long
    For i = LBound(rules) To UBound(rules)
        If rules(i).Enabled Then
            ApplyAggRule rules(i)
        End If
    Next i
    
    SpeedOff
    MsgBox "ノーコード集計ツールの処理が完了しました。", vbInformation
End Sub
VB

1つの集計ルールを適用する汎用処理

ここが心臓部です。

Private Sub ApplyAggRule(ByRef rule As AggRule)
    Dim wsS As Worksheet, wsT As Worksheet
    Set wsS = ThisWorkbook.Worksheets(rule.SourceSheet)
    
    On Error Resume Next
    Set wsT = ThisWorkbook.Worksheets(rule.TargetSheet)
    On Error GoTo 0
    If wsT Is Nothing Then
        Set wsT = ThisWorkbook.Worksheets.Add
        wsT.Name = rule.TargetSheet
    Else
        wsT.Cells.Clear
    End If
    
    Dim lastRow As Long, lastCol As Long
    lastRow = wsS.Cells(wsS.Rows.Count, 1).End(xlUp).Row
    lastCol = wsS.Cells(1, wsS.Columns.Count).End(xlToLeft).Column
    If lastRow < 2 Then Exit Sub
    
    Dim data As Variant
    data = wsS.Range(wsS.Cells(1, 1), wsS.Cells(lastRow, lastCol)).Value
    
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    dict.CompareMode = 1
    
    Dim r As Long
    Dim key As String
    Dim cols As Variant
    cols = rule.GroupCols
    
    For r = 2 To lastRow
        key = BuildGroupKey(Application.Index(data, r, 0), cols)
        If Not dict.Exists(key) Then
            Dim av As AggValue
            av.Sum = 0
            av.Count = 0
            av.Max = -1E+308
            av.Min = 1E+308
            av.First = data(r, rule.ValueCol)
            dict.Add key, av
        End If
        
        Dim cur As AggValue
        cur = dict(key)
        
        Dim v As Variant
        v = data(r, rule.ValueCol)
        If IsNumeric(v) Then
            cur.Sum = cur.Sum + CDbl(v)
            If CDbl(v) > cur.Max Then cur.Max = CDbl(v)
            If CDbl(v) < cur.Min Then cur.Min = CDbl(v)
        End If
        cur.Count = cur.Count + 1
        
        dict(key) = cur
    Next
    
    Dim outRows As Long
    outRows = dict.Count
    
    Dim outData As Variant
    ReDim outData(1 To outRows + 1, 1 To UBound(cols) + 2)
    
    Dim i As Long, idx As Long
    For i = 1 To UBound(cols)
        outData(1, i) = wsS.Cells(1, cols(i - 1)).Value
    Next
    outData(1, UBound(cols) + 1) = rule.AggFunc
    
    idx = 2
    Dim k As Variant
    For Each k In dict.Keys
        Dim parts As Variant
        parts = Split(CStr(k), "||")
        For i = 0 To UBound(parts)
            outData(idx, i + 1) = parts(i)
        Next
        
        Dim res As Variant
        Dim av2 As AggValue
        av2 = dict(k)
        Select Case rule.AggFunc
            Case "SUM": res = av2.Sum
            Case "COUNT": res = av2.Count
            Case "MAX": res = av2.Max
            Case "MIN": res = av2.Min
            Case "FIRST": res = av2.First
            Case Else: res = av2.Sum
        End Select
        
        outData(idx, UBound(cols) + 1) = res
        idx = idx + 1
    Next
    
    wsT.Range(wsT.Cells(1, rule.OutStartCol), wsT.Cells(outRows + 1, rule.OutStartCol + UBound(cols))).Value = outData
    wsT.Columns.AutoFit
End Sub
VB

ここでの重要ポイントを整理します。

集計元シートを丸ごと配列に読み込んでいるので高速。
グループキーは BuildGroupKey で文字列化し、Dictionary のキーとして使う。
AggValue に Sum, Count, Max, Min, First を全部持たせておき、AggFunc に応じてどれを出すか切り替える。
出力シートはルールごとにクリアしてから書き出すので、毎回「最新の集計結果」だけが残る。


例題:売上明細を「店舗×月」で合計するノーコード集計

想定する元データ(Data)

Data シートに、次のような列があるとします。

A列:日付
B列:店舗コード
C列:商品コード
D列:数量
E列:売上金額

やりたいことは、「店舗×月ごとの売上金額合計」を出すことです。

月は、日付から「年月」を作ってグループ化しても良いですが、まずはシンプルに「店舗×日付」でグループ化する例で考えます。

ConfigAgg シートに、次のように書きます。

A列:Y
B列:Data
C列:Agg
D列:A,B
E列:E
F列:SUM
G列:A

これで、「Data を A列(日付)と B列(店舗コード)でグループ化し、E列(売上金額)を合計して Agg に出す」という設定になります。

RunAggTool を実行すると、Agg シートには

日付
店舗コード
SUM

という三列が出て、行ごとに「日付×店舗」の売上合計が並びます。

もし「店舗×月」で集計したければ、Data シートに「年月」列を追加しておき、
ConfigAgg の GroupCols を「F,B」のように変えるだけで対応できます(F列が年月だと仮定)。


重要ポイントの深掘り:ノーコード集計ツールを“現場で回る”形にするコツ

集計関数の種類を増やすときの考え方

今のテンプレは、SUM, COUNT, MAX, MIN, FIRST に対応しています。
もし「平均」を追加したければ、AggValue に Sum と Count が既にあるので、AggFunc=”AVG” のときに Sum / Count を出すだけで済みます。

Select Case rule.AggFunc の中に AVG を追加し、
res = IIf(av2.Count = 0, 0, av2.Sum / av2.Count)
のように書けばOKです。

このように、「AggValue に必要な情報を先に全部持たせておく」と、
あとから集計関数を増やすのがとても楽になります。

ConfigAgg の「検証」を入れておくべき理由

完全ノーコードにすると、現場の人が自由に設定を変えられる反面、
シート名のタイプミス、列指定のミス、集計関数名の誤記なども起こりやすくなります。

実務で使うなら、RunAggTool の最初に

指定された SourceSheet/TargetSheet が存在するか
GroupCols に指定された列が存在するか
AggFunc が許可された値(SUM, COUNT, MAX, MIN, AVG など)か

をチェックし、問題があれば処理を止めてメッセージを出す、という“安全装置”を付けておくと安心です。

ピボットテーブルとの住み分け

「集計ならピボットでいいのでは?」という疑問も出てきます。
ピボットは対話的な分析には最高ですが、

毎回同じ集計を同じ形で出したい
複数パターンの集計を一括で出したい
集計結果をさらに別のマクロやJOINの入力に使いたい

というときには、今回のような「配列+Dictionaryベースの集計エンジン」の方が向いています。

ConfigAgg に集計パターンを並べておけば、
「店舗×日」「店舗×月」「商品×月」「店舗×商品」など、
複数の集計を一気に回して、それぞれ別シートに出すことができます。


まとめ:ノーコード集計ツールは「集計を表にして、コードはエンジンに閉じ込める」発想

このテンプレの本質は、次の三つです。

どのシートを、どの列でグループ化し、どの列をどの関数で集計するかを、ConfigAgg という表にすべて書き出す。
VBA側は、その表を読み取り、配列と Dictionary を使って高速に集計する汎用エンジンだけを持つ。
集計内容を変えたいときは、ConfigAgg の行を追加・修正するだけで、コードは一切触らない。

これができると、「売上集計」「アクセスログ集計」「アンケート集計」など、
集計パターンがいくつ増えても、ツールは1本で済みます。

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