マスタ+集計の合成
「明細から集計した指標(合計・件数・平均・最新日など)と、マスタの属性(名称・カテゴリ・部署など)を“ひとつの表”にまとめたい」——そのための安全・高速テンプレを、初心者向けにかみ砕いて用意しました。基本は「配列→辞書で集計」「キーでJOIN」「新シートに整えて出力」です。
ワークフローの全体像
- データ取り込み: 明細とマスタを CurrentRegion で配列に読み込む
- 集計: キー別に合計・件数・平均・最大/最新などを辞書で計算
- JOIN: 集計結果にマスタ属性をキーで横付け
- 出力: 新シートに一括貼り付け、ヘッダー・書式を整える
- 監査: 未一致キーや重複キーを一覧化してチェック
単一キーで「明細→集計」して「マスタ→JOIN」(基本)
「コード」単位で明細を集計し、名称・カテゴリを横付けして新シートへ出力します。
Option Explicit
Private Function EnsureSheet(ByVal sheetName As String, Optional ByVal clear As Boolean = True) As Worksheet
Dim ws As Worksheet
On Error Resume Next
Set ws = Worksheets(sheetName)
On Error GoTo 0
If ws Is Nothing Then
Set ws = Worksheets.Add(After:=Worksheets(Worksheets.Count))
ws.Name = sheetName
End If
If clear Then ws.Cells.Clear
Set EnsureSheet = ws
End Function
Sub Compose_MasterPlusAggregation_Basic()
'明細: Sheet("明細") A=コード, B=数量, C=金額, D=日付
'マスタ: Sheet("マスタ") A=コード, B=名称, C=カテゴリ
'出力: Sheet("合成結果") … コード, 名称, カテゴリ, 合計金額, 件数, 平均金額, 最新日付
Dim wsD As Worksheet: Set wsD = Worksheets("明細")
Dim wsM As Worksheet: Set wsM = Worksheets("マスタ")
Dim vD As Variant: vD = wsD.Range("A1").CurrentRegion.Value
Dim vM As Variant: vM = wsM.Range("A1").CurrentRegion.Value
'1) 集計: コード→(sum, cnt, maxDate)
Dim agg As Object: Set agg = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vD, 1)
k = UCase$(Trim$(CStr(vD(i, 1))))
If Len(k) = 0 Then GoTo contD
Dim amt As Double: amt = CDbl(Val(vD(i, 3))) '金額
Dim dte As Date
If IsDate(vD(i, 4)) Then dte = CDate(vD(i, 4)) Else dte = 0
If agg.Exists(k) Then
agg(k)(0) = agg(k)(0) + amt 'sum
agg(k)(1) = agg(k)(1) + 1 'cnt
If dte > agg(k)(2) Then agg(k)(2) = dte 'latest
Else
agg.Add k, Array(amt, 1, dte)
End If
contD:
Next
'2) マスタ辞書: コード→(名称,カテゴリ)
Dim dictM As Object: Set dictM = CreateObject("Scripting.Dictionary")
For i = 2 To UBound(vM, 1)
k = UCase$(Trim$(CStr(vM(i, 1))))
If Len(k) > 0 Then dictM(k) = Array(CStr(vM(i, 2)), CStr(vM(i, 3)))
Next
'3) 出力配列(キーの和集合:集計にあるキーをベース)
Dim keys As Variant: keys = agg.Keys
Dim n As Long: n = UBound(keys) + 1
Dim out() As Variant: ReDim out(1 To n + 1, 1 To 7)
out(1, 1) = "コード": out(1, 2) = "名称": out(1, 3) = "カテゴリ"
out(1, 4) = "合計金額": out(1, 5) = "件数": out(1, 6) = "平均金額": out(1, 7) = "最新日付"
Dim r As Long
For r = 0 To UBound(keys)
k = keys(r)
Dim sumAmt As Double: sumAmt = agg(k)(0)
Dim cnt As Long: cnt = agg(k)(1)
Dim avgAmt As Double: avgAmt = IIf(cnt > 0, sumAmt / cnt, 0)
Dim latest As Date: latest = agg(k)(2)
out(r + 2, 1) = keys(r)
If dictM.Exists(k) Then
out(r + 2, 2) = dictM(k)(0)
out(r + 2, 3) = dictM(k)(1)
Else
out(r + 2, 2) = "#N/A"
out(r + 2, 3) = ""
End If
out(r + 2, 4) = sumAmt
out(r + 2, 5) = cnt
out(r + 2, 6) = avgAmt
out(r + 2, 7) = IIf(latest = 0, "", latest)
Next
'4) 新シートへ出力+書式
Dim wsOut As Worksheet: Set wsOut = EnsureSheet("合成結果", True)
wsOut.Range("A1").Resize(n + 1, 7).Value = out
wsOut.Rows(1).Font.Bold = True
wsOut.Columns.AutoFit
wsOut.Range("D2:D" & n + 1).NumberFormat = "#,##0"
wsOut.Range("E2:E" & n + 1).NumberFormat = "0"
wsOut.Range("F2:F" & n + 1).NumberFormat = "#,##0.00"
wsOut.Range("G2:G" & n + 1).NumberFormat = "yyyy-mm-dd"
End Sub
VB- ポイント
- 集計指標の自由度: 合計・件数・平均・最新日を同時に計算。
- JOINの片寄り: 集計に出たキーを基準に横付け(完全結合にしたい場合は後述)。
複数キー(例:コード×年月)で集計+マスタJOIN
年月別の集計指標を持ちながら、マスタ属性を横付けします。
Sub Compose_MasterPlusAggregation_MultiKey()
'明細: A=コード, B=日付, C=金額
'マスタ: A=コード, B=名称, C=カテゴリ
'出力: コード, 名称, カテゴリ, 年月, 合計, 件数
Dim vD As Variant: vD = Worksheets("明細").Range("A1").CurrentRegion.Value
Dim vM As Variant: vM = Worksheets("マスタ").Range("A1").CurrentRegion.Value
'1) マスタ辞書(コード→(名称,カテゴリ))
Dim dictM As Object: Set dictM = CreateObject("Scripting.Dictionary")
Dim i As Long, kCode As String
For i = 2 To UBound(vM, 1)
kCode = UCase$(Trim$(CStr(vM(i, 1))))
If Len(kCode) > 0 Then dictM(kCode) = Array(CStr(vM(i, 2)), CStr(vM(i, 3)))
Next
'2) 集計辞書(key="コード|yyyy-mm" → (sum, cnt))
Dim agg As Object: Set agg = CreateObject("Scripting.Dictionary")
Dim ym As String, key As String
For i = 2 To UBound(vD, 1)
kCode = UCase$(Trim$(CStr(vD(i, 1))))
If Not IsDate(vD(i, 2)) Then GoTo contD
ym = Format$(CDate(vD(i, 2)), "yyyy-mm")
key = kCode & "|" & ym
Dim amt As Double: amt = CDbl(Val(vD(i, 3)))
If agg.Exists(key) Then
agg(key)(0) = agg(key)(0) + amt
agg(key)(1) = agg(key)(1) + 1
Else
agg.Add key, Array(amt, 1)
End If
contD:
Next
'3) 出力配列(集計キーを全件)
Dim keys As Variant: keys = agg.Keys
Dim n As Long: n = UBound(keys) + 1
Dim out() As Variant: ReDim out(1 To n + 1, 1 To 6)
out(1, 1) = "コード": out(1, 2) = "名称": out(1, 3) = "カテゴリ": out(1, 4) = "年月": out(1, 5) = "合計": out(1, 6) = "件数"
Dim r As Long
For r = 0 To UBound(keys)
key = keys(r)
Dim parts() As String: parts = Split(key, "|")
kCode = parts(0): ym = parts(1)
out(r + 2, 1) = kCode
If dictM.Exists(kCode) Then
out(r + 2, 2) = dictM(kCode)(0)
out(r + 2, 3) = dictM(kCode)(1)
Else
out(r + 2, 2) = "#N/A": out(r + 2, 3) = ""
End If
out(r + 2, 4) = ym
out(r + 2, 5) = agg(key)(0)
out(r + 2, 6) = agg(key)(1)
Next
Dim wsOut As Worksheet: Set wsOut = EnsureSheet("合成結果_複数キー", True)
wsOut.Range("A1").Resize(n + 1, 6).Value = out
wsOut.Rows(1).Font.Bold = True
wsOut.Columns.AutoFit
wsOut.Range("E2:E" & n + 1).NumberFormat = "#,##0"
End Sub
VB- ポイント
- 年月の統一:
Format$(date,"yyyy-mm")で型・表記を揃える。 - 複合キー:
"コード|年月"のような安全な区切り文字を使用。
- 年月の統一:
完全結合で「集計+マスタ」を合成(両側のキーを全部残す)
集計側にあるがマスタに無いコード、マスタにあるが集計に無いコードも可視化します。
Sub Compose_MasterPlusAggregation_FullJoin()
'前段のBasicで作った agg と dictM を想定(必要ならその場で再作成)
Dim wsD As Worksheet: Set wsD = Worksheets("明細")
Dim wsM As Worksheet: Set wsM = Worksheets("マスタ")
Dim vD As Variant: vD = wsD.Range("A1").CurrentRegion.Value
Dim vM As Variant: vM = wsM.Range("A1").CurrentRegion.Value
'集計辞書(コード→(sum, cnt, avg))
Dim agg As Object: Set agg = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vD, 1)
k = UCase$(Trim$(CStr(vD(i, 1))))
Dim amt As Double: amt = CDbl(Val(vD(i, 3)))
If agg.Exists(k) Then
agg(k)(0) = agg(k)(0) + amt
agg(k)(1) = agg(k)(1) + 1
Else
agg.Add k, Array(amt, 1)
End If
Next
'平均の準備は出力で都度計算
'マスタ辞書(コード→(名称,カテゴリ))
Dim dictM As Object: Set dictM = CreateObject("Scripting.Dictionary")
For i = 2 To UBound(vM, 1)
k = UCase$(Trim$(CStr(vM(i, 1))))
If Len(k) > 0 Then dictM(k) = Array(CStr(vM(i, 2)), CStr(vM(i, 3)))
Next
'キーの和集合
Dim all As Object: Set all = CreateObject("Scripting.Dictionary")
For Each k In agg.Keys: all(k) = True: Next
For Each k In dictM.Keys: all(k) = True: Next
'出力配列
Dim n As Long: n = all.Count
Dim out() As Variant: ReDim out(1 To n + 1, 1 To 6)
out(1, 1) = "コード": out(1, 2) = "名称": out(1, 3) = "カテゴリ"
out(1, 4) = "合計金額": out(1, 5) = "件数": out(1, 6) = "平均金額"
Dim r As Long: r = 2
Dim key As Variant
For Each key In all.Keys
Dim sumAmt As Double: sumAmt = IIf(agg.Exists(key), agg(key)(0), 0)
Dim cnt As Long: cnt = IIf(agg.Exists(key), agg(key)(1), 0)
out(r, 1) = key
out(r, 2) = IIf(dictM.Exists(key), dictM(key)(0), "#N/A")
out(r, 3) = IIf(dictM.Exists(key), dictM(key)(1), "")
out(r, 4) = sumAmt
out(r, 5) = cnt
out(r, 6) = IIf(cnt > 0, sumAmt / cnt, 0)
r = r + 1
Next
Dim wsOut As Worksheet: Set wsOut = EnsureSheet("合成結果_完全結合", True)
wsOut.Range("A1").Resize(n + 1, 6).Value = out
wsOut.Rows(1).Font.Bold = True
wsOut.Columns.AutoFit
wsOut.Range("D2:D" & n + 1).NumberFormat = "#,##0"
wsOut.Range("E2:E" & n + 1).NumberFormat = "0"
wsOut.Range("F2:F" & n + 1).NumberFormat = "#,##0.00"
End Sub
VB- ポイント
- 完全結合: キーの和集合で両側の値を横付け、片側欠損は空欄や0で補完。
- 監査に強い: マスタ未登録の集計キー、集計ゼロのマスタキーが一目でわかる。
見出し名で列特定して壊れない(柔軟版)
列順が変わっても動くように、見出し名で明細列・マスタ列を取得してから同じ流れで合成します。
Private Function FindHeader(ByVal headerRow As Range, ByVal name As String) As Long
Dim hit As Range
Set hit = headerRow.Find(What:=name, LookAt:=xlWhole, LookIn:=xlValues, MatchCase:=False)
FindHeader = IIf(hit Is Nothing, 0, hit.Column)
End Function
Sub Compose_MasterPlusAggregation_ByHeaders()
Dim rgD As Range: Set rgD = Worksheets("明細").Range("A1").CurrentRegion
Dim rgM As Range: Set rgM = Worksheets("マスタ").Range("A1").CurrentRegion
Dim vD As Variant: vD = rgD.Value
Dim vM As Variant: vM = rgM.Value
'見出しを動的に取得
Dim cCodeD As Long: cCodeD = FindHeader(rgD.Rows(1), "コード")
Dim cAmtD As Long: cAmtD = FindHeader(rgD.Rows(1), "金額")
Dim cDateD As Long: cDateD = FindHeader(rgD.Rows(1), "日付")
Dim cCodeM As Long: cCodeM = FindHeader(rgM.Rows(1), "コード")
Dim cNameM As Long: cNameM = FindHeader(rgM.Rows(1), "名称")
Dim cCatM As Long: cCatM = FindHeader(rgM.Rows(1), "カテゴリ")
If cCodeD * cAmtD * cDateD * cCodeM * cNameM * cCatM = 0 Then
MsgBox "見出し不足": Exit Sub
End If
'集計
Dim agg As Object: Set agg = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vD, 1)
k = UCase$(Trim$(CStr(vD(i, cCodeD))))
Dim amt As Double: amt = CDbl(Val(vD(i, cAmtD)))
Dim dte As Date: dte = IIf(IsDate(vD(i, cDateD)), CDate(vD(i, cDateD)), 0)
If agg.Exists(k) Then
agg(k)(0) = agg(k)(0) + amt
agg(k)(1) = agg(k)(1) + 1
If dte > agg(k)(2) Then agg(k)(2) = dte
Else
agg.Add k, Array(amt, 1, dte)
End If
Next
'マスタ辞書
Dim dictM As Object: Set dictM = CreateObject("Scripting.Dictionary")
For i = 2 To UBound(vM, 1)
k = UCase$(Trim$(CStr(vM(i, cCodeM))))
dictM(k) = Array(CStr(vM(i, cNameM)), CStr(vM(i, cCatM)))
Next
'出力
Dim keys As Variant: keys = agg.Keys
Dim n As Long: n = UBound(keys) + 1
Dim out() As Variant: ReDim out(1 To n + 1, 1 To 7)
out(1, 1) = "コード": out(1, 2) = "名称": out(1, 3) = "カテゴリ": out(1, 4) = "合計金額"
out(1, 5) = "件数": out(1, 6) = "平均金額": out(1, 7) = "最新日付"
Dim r As Long
For r = 0 To UBound(keys)
k = keys(r)
Dim sumAmt As Double: sumAmt = agg(k)(0)
Dim cnt As Long: cnt = agg(k)(1)
out(r + 2, 1) = k
out(r + 2, 2) = IIf(dictM.Exists(k), dictM(k)(0), "#N/A")
out(r + 2, 3) = IIf(dictM.Exists(k), dictM(k)(1), "")
out(r + 2, 4) = sumAmt
out(r + 2, 5) = cnt
out(r + 2, 6) = IIf(cnt > 0, sumAmt / cnt, 0)
out(r + 2, 7) = IIf(agg(k)(2) = 0, "", agg(k)(2))
Next
Dim wsOut As Worksheet: Set wsOut = EnsureSheet("合成結果_ByHeaders", True)
wsOut.Range("A1").Resize(n + 1, 7).Value = out
wsOut.Rows(1).Font.Bold = True
wsOut.Columns.AutoFit
End Sub
VB- ポイント
- 壊れない: 見出し名で動的に列取得し、現場の列順変動に対応。
- 早期検知: 見出し不足はメッセージで止めて原因を明確化。
よくある落とし穴と対策
- キー表記揺れでJOIN漏れ
- 対策: 正規化:
Trim/UCaseを徹底。必要なら半角化や記号除去も。
- 対策: 正規化:
- 数値が文字列で平均がズレる
- 対策: 数値化:
Val経由でCDblに。小数は書式で丸め。
- 対策: 数値化:
- 日付の型が混在
- 対策: 型統一:
CDateとFormat$で統一表記。最新日付はDateValueで時刻除去も可。
- 対策: 型統一:
- 大規模で遅い
- 対策: 配列+辞書+一括貼付。 前後で
ScreenUpdating=False、Calculation=Manualを使うと安定。
- 対策: 配列+辞書+一括貼付。 前後で
- 完全結合の片側欠損が見えにくい
- 対策: 明示補完: 文字項目は「#N/A」、数値は0で“欠損”を見える化。
例題で練習
'例1:単一キーで合計・件数・平均・最新日+マスタを合成
Sub Example_ComposeBasic()
Compose_MasterPlusAggregation_Basic
End Sub
'例2:コード×年月で集計してマスタを合成
Sub Example_ComposeMultiKey()
Compose_MasterPlusAggregation_MultiKey
End Sub
'例3:見出し名で列特定して壊れない合成
Sub Example_ComposeByHeaders()
Compose_MasterPlusAggregation_ByHeaders
End Sub
'例4:集計とマスタの完全結合で両側のキーを全部出す
Sub Example_ComposeFullJoin()
Compose_MasterPlusAggregation_FullJoin
End Sub
VB