複数キーJOIN
「部署×年月」「コード×枝番」など、2つ以上のキーで明細にマスタ情報を付けたり、集約表同士を横に突き合わせたいときのテンプレをまとめました。初心者でも壊れないやり方を、関数・配列+辞書・見出し名対応で用意しています。
使い分けの指針
- 最短・少量: ヘルパー列でキー連結→VLOOKUP(式投入→値化)
- 列増減に強い: ヘルパー列+INDEX/MATCH(列番号不要で堅牢)
- 大量・高速: 配列+辞書で「複合キー」JOIN(10万行規模でも実用)
- 横統合(左結合・フル結合): 辞書でキー→値を作り、基準表に横付け
- 列順が変わる現場: 見出し名から列特定して安全に参照
最短:ヘルパー列でキー連結→VLOOKUP(式→値化)
Sub JoinMultiKey_VLookup()
'明細: A=部署, B=年月, C=金額
'マスタ: A=部署, B=年月, C=名称, D=カテゴリ
Dim wsD As Worksheet: Set wsD = Worksheets("明細")
Dim wsM As Worksheet: Set wsM = Worksheets("マスタ")
'ヘルパー列(複合キー)を両方に作成:部署 & "|" & 年月
Dim lastD As Long: lastD = wsD.Cells(wsD.Rows.Count, "A").End(xlUp).Row
Dim lastM As Long: lastM = wsM.Cells(wsM.Rows.Count, "A").End(xlUp).Row
wsD.Range("E1").Value = "key"
wsD.Range("E2:E" & lastD).FormulaR1C1 = "=RC1&""|""&RC2"
wsD.Range("E2:E" & lastD).Value = wsD.Range("E2:E" & lastD).Value
wsM.Range("E1").Value = "key"
wsM.Range("E2:E" & lastM).FormulaR1C1 = "=RC1&""|""&RC2"
wsM.Range("E2:E" & lastM).Value = wsM.Range("E2:E" & lastM).Value
'VLOOKUPで名称・カテゴリを付与(値化)
Dim tblM As Range: Set tblM = wsM.Range("E1").CurrentRegion 'E=key起点
wsD.Range("F1:G1").Value = Array("名称", "カテゴリ")
With wsD.Range("F2:F" & lastD)
.FormulaR1C1 = "=IFERROR(VLOOKUP(RC5," & tblM.Address(True, True, xlA1, True) & ",3,FALSE),""#N/A"")"
.Value = .Value
End With
With wsD.Range("G2:G" & lastD)
.FormulaR1C1 = "=IFERROR(VLOOKUP(RC5," & tblM.Address(True, True, xlA1, True) & ",4,FALSE),""#N/A"")"
.Value = .Value
End With
End Sub
VB- ポイント
- ヘルパー列: 複合キーは区切り文字(例:”|”)で連結して1列に。
- 値化必須: 式は処理後に値化して軽く・安全にする。
堅牢:ヘルパー列+INDEX/MATCH(列番号不要)
Sub JoinMultiKey_IndexMatch()
Dim wsD As Worksheet: Set wsD = Worksheets("明細")
Dim wsM As Worksheet: Set wsM = Worksheets("マスタ")
Dim lastD As Long: lastD = wsD.Cells(wsD.Rows.Count, "A").End(xlUp).Row
Dim lastM As Long: lastM = wsM.Cells(wsM.Rows.Count, "A").End(xlUp).Row
'複合キー列(E列)を両方に
wsD.Range("E1").Value = "key"
wsD.Range("E2:E" & lastD).FormulaR1C1 = "=RC1&""|""&RC2"
wsD.Range("E2:E" & lastD).Value = wsD.Range("E2:E" & lastD).Value
wsM.Range("E1").Value = "key"
wsM.Range("E2:E" & lastM).FormulaR1C1 = "=RC1&""|""&RC2"
wsM.Range("E2:E" & lastM).Value = wsM.Range("E2:E" & lastM).Value
'INDEX/MATCHで付与(名称=列C, カテゴリ=列D)
Dim rngKeyM As Range: Set rngKeyM = wsM.Range("E:E")
Dim rngNameM As Range: Set rngNameM = wsM.Range("C:C")
Dim rngCatM As Range: Set rngCatM = wsM.Range("D:D")
wsD.Range("F1:G1").Value = Array("名称", "カテゴリ")
With wsD.Range("F2:F" & lastD)
.FormulaR1C1 = "=IFERROR(INDEX(" & rngNameM.Address(True, True, xlA1, True) & ",MATCH(RC5," & rngKeyM.Address(True, True, xlA1, True) & ",0)),""#N/A"")"
.Value = .Value
End With
With wsD.Range("G2:G" & lastD)
.FormulaR1C1 = "=IFERROR(INDEX(" & rngCatM.Address(True, True, xlA1, True) & ",MATCH(RC5," & rngKeyM.Address(True, True, xlA1, True) & ",0)),""#N/A"")"
.Value = .Value
End With
End Sub
VB- ポイント
- 列増減に強い: 参照は列名(列範囲)に紐付ける。
- VLOOKUPの列番号ズレ問題を回避。
爆速・堅牢:配列+辞書で複数キーJOIN(複合キー)
Sub JoinMultiKey_Dictionary()
'明細: A=部署, B=年月, C=金額
'マスタ: A=部署, B=年月, C=名称, D=カテゴリ
Dim wsD As Worksheet: Set wsD = Worksheets("明細")
Dim wsM As Worksheet: Set wsM = Worksheets("マスタ")
'1) マスタ→辞書(key="部署|年月" → (名称,カテゴリ))
Dim vm As Variant: vm = wsM.Range("A1").CurrentRegion.Value
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vm, 1)
k = UCase$(Trim$(CStr(vm(i, 1)))) & "|" & UCase$(Trim$(CStr(vm(i, 2))))
dict(k) = Array(CStr(vm(i, 3)), CStr(vm(i, 4)))
Next
'2) 明細→配列に読み込み、名称・カテゴリ列を追加して書き戻し
Dim vd As Variant: vd = wsD.Range("A1").CurrentRegion.Value
Dim out() As Variant: ReDim out(1 To UBound(vd, 1), 1 To UBound(vd, 2) + 2)
'ヘッダー
Dim c As Long: For c = 1 To UBound(vd, 2): out(1, c) = vd(1, c): Next
out(1, UBound(vd, 2) + 1) = "名称": out(1, UBound(vd, 2) + 2) = "カテゴリ"
'行ループ
Dim r As Long
For r = 2 To UBound(vd, 1)
For c = 1 To UBound(vd, 2): out(r, c) = vd(r, c): Next
k = UCase$(Trim$(CStr(vd(r, 1)))) & "|" & UCase$(Trim$(CStr(vd(r, 2))))
If dict.Exists(k) Then
out(r, UBound(vd, 2) + 1) = dict(k)(0)
out(r, UBound(vd, 2) + 2) = dict(k)(1)
Else
out(r, UBound(vd, 2) + 1) = "#N/A"
out(r, UBound(vd, 2) + 2) = "#N/A"
End If
Next
wsD.Range("A1").Resize(UBound(out, 1), UBound(out, 2)).Value = out
End Sub
VB- ポイント
- キー正規化:
Trim/UCaseで表記揺れを吸収。 - セル往復ゼロ: 範囲→配列→辞書→一括書き戻しで高速。
- キー正規化:
横統合:複数キーで左結合・フル結合(基準表に他指標を横付け)
Sub MergeMultiKey_LeftJoin()
'基準: Sheet("基準") A=部署, B=年月, C=合計
'追加: Sheet("他指標") A=部署, B=年月, C=他合計
'出力: Sheet("統合")
Dim wsB As Worksheet: Set wsB = Worksheets("基準")
Dim wsO As Worksheet: Set wsO = Worksheets("他指標")
Dim wsOut As Worksheet
On Error Resume Next
Set wsOut = Worksheets("統合")
If wsOut Is Nothing Then Set wsOut = Worksheets.Add: wsOut.Name = "統合"
On Error GoTo 0
wsOut.Cells.Clear
wsOut.Range("A1:D1").Value = Array("部署", "年月", "合計", "他合計")
'追加辞書(key="部署|年月" → 他合計)
Dim vo As Variant: vo = wsO.Range("A1").CurrentRegion.Value
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vo, 1)
k = UCase$(Trim$(CStr(vo(i, 1)))) & "|" & UCase$(Trim$(CStr(vo(i, 2))))
dict(k) = Val(vo(i, 3))
Next
'基準を出力しながら横付け
Dim vb As Variant: vb = wsB.Range("A1").CurrentRegion.Value
Dim rOut As Long: rOut = 2
For i = 2 To UBound(vb, 1)
k = UCase$(Trim$(CStr(vb(i, 1)))) & "|" & UCase$(Trim$(CStr(vb(i, 2))))
wsOut.Cells(rOut, 1).Value = vb(i, 1)
wsOut.Cells(rOut, 2).Value = vb(i, 2)
wsOut.Cells(rOut, 3).Value = Val(vb(i, 3))
wsOut.Cells(rOut, 4).Value = IIf(dict.Exists(k), dict(k), 0)
rOut = rOut + 1
Next
wsOut.Columns.AutoFit
End Sub
VBSub MergeMultiKey_FullJoin()
'基準・追加の両キーを和集合にしてフル結合
Dim wsB As Worksheet: Set wsB = Worksheets("基準")
Dim wsO As Worksheet: Set wsO = Worksheets("他指標")
Dim wsOut As Worksheet
On Error Resume Next
Set wsOut = Worksheets("統合")
If wsOut Is Nothing Then Set wsOut = Worksheets.Add: wsOut.Name = "統合"
On Error GoTo 0
wsOut.Cells.Clear
wsOut.Range("A1:E1").Value = Array("部署", "年月", "合計", "他合計", "キー")
Dim vb As Variant: vb = wsB.Range("A1").CurrentRegion.Value
Dim vo As Variant: vo = wsO.Range("A1").CurrentRegion.Value
Dim base As Object: Set base = CreateObject("Scripting.Dictionary")
Dim other As Object: Set other = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vb, 1)
k = UCase$(Trim$(CStr(vb(i, 1)))) & "|" & UCase$(Trim$(CStr(vb(i, 2))))
base(k) = Array(CStr(vb(i, 1)), CStr(vb(i, 2)), Val(vb(i, 3)))
Next
For i = 2 To UBound(vo, 1)
k = UCase$(Trim$(CStr(vo(i, 1)))) & "|" & UCase$(Trim$(CStr(vo(i, 2))))
other(k) = Val(vo(i, 3))
Next
'キーの和集合
Dim all As Object: Set all = CreateObject("Scripting.Dictionary")
For Each k In base.Keys: all(k) = True: Next
For Each k In other.Keys: all(k) = True: Next
Dim rOut As Long: rOut = 2
Dim key As Variant
For Each key In all.Keys
Dim dep As String, ym As String, sumB As Double, sumO As Double
If base.Exists(key) Then
dep = base(key)(0): ym = base(key)(1): sumB = base(key)(2)
Else
dep = Split(key, "|")(0): ym = Split(key, "|")(1): sumB = 0
End If
sumO = IIf(other.Exists(key), other(key), 0)
wsOut.Cells(rOut, 1).Value = dep
wsOut.Cells(rOut, 2).Value = ym
wsOut.Cells(rOut, 3).Value = sumB
wsOut.Cells(rOut, 4).Value = sumO
wsOut.Cells(rOut, 5).Value = key
rOut = rOut + 1
Next
wsOut.Columns.AutoFit
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
VBSub JoinMultiKey_ByHeaders()
'見出し名で「部署」「年月」を特定して複合キーJOIN
Dim wsD As Worksheet: Set wsD = Worksheets("明細")
Dim wsM As Worksheet: Set wsM = Worksheets("マスタ")
Dim rgD As Range: Set rgD = wsD.Range("A1").CurrentRegion
Dim rgM As Range: Set rgM = wsM.Range("A1").CurrentRegion
Dim cDepD As Long: cDepD = FindHeader(rgD.Rows(1), "部署")
Dim cYmD As Long: cYmD = FindHeader(rgD.Rows(1), "年月")
Dim cDepM As Long: cDepM = FindHeader(rgM.Rows(1), "部署")
Dim cYmM As Long: cYmM = FindHeader(rgM.Rows(1), "年月")
Dim cNameM As Long: cNameM = FindHeader(rgM.Rows(1), "名称")
Dim cCatM As Long: cCatM = FindHeader(rgM.Rows(1), "カテゴリ")
If cDepD*cYmD*cDepM*cYmM*cNameM*cCatM = 0 Then MsgBox "見出しが不足しています": Exit Sub
Dim vm As Variant: vm = rgM.Value
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vm, 1)
k = UCase$(Trim$(CStr(vm(i, cDepM)))) & "|" & UCase$(Trim$(CStr(vm(i, cYmM))))
dict(k) = Array(CStr(vm(i, cNameM)), CStr(vm(i, cCatM)))
Next
Dim vd As Variant: vd = rgD.Value
Dim out() As Variant: ReDim out(1 To UBound(vd, 1), 1 To UBound(vd, 2) + 2)
Dim c As Long: For c = 1 To UBound(vd, 2): out(1, c) = vd(1, c): Next
out(1, UBound(vd, 2) + 1) = "名称": out(1, UBound(vd, 2) + 2) = "カテゴリ"
Dim r As Long
For r = 2 To UBound(vd, 1)
For c = 1 To UBound(vd, 2): out(r, c) = vd(r, c): Next
k = UCase$(Trim$(CStr(vd(r, cDepD)))) & "|" & UCase$(Trim$(CStr(vd(r, cYmD))))
If dict.Exists(k) Then
out(r, UBound(vd, 2) + 1) = dict(k)(0)
out(r, UBound(vd, 2) + 2) = dict(k)(1)
Else
out(r, UBound(vd, 2) + 1) = "#N/A"
out(r, UBound(vd, 2) + 2) = "#N/A"
End If
Next
wsD.Range("A1").Resize(UBound(out, 1), UBound(out, 2)).Value = out
End Sub
VB- ポイント
- 列順変動に耐性: 見出し名から動的に列番号を取得。
- 監査しやすさ: 未一致は「#N/A」で可視化。
よくある落とし穴と対策
- キー表記揺れでJOIN失敗
- 対策: 正規化:
Trim/UCase、必要なら半角化や不要記号の置換も。
- 対策: 正規化:
- 年月の型が違う(2024-01と日付型など)
- 対策: 統一: 連結前に
Format$(DateValue(日付),"yyyy-mm")で同じ表記へ。
- 対策: 統一: 連結前に
- 区切り文字が値と衝突
- 対策: 安全な区切り:
"|"など、実データに現れないものを採用。
- 対策: 安全な区切り:
- 数値が文字列で合計ずれ
- 対策: 数値化:
Valを通す、式は.Value = .Valueで値化。
- 対策: 数値化:
- 大規模で遅い
- 対策: 配列処理+安全ラップ: ScreenUpdating/Calculation/Events を停止→復帰。
例題で練習
'例1:複合キー(部署|年月)をヘルパー列で作ってVLOOKUPで付与
Sub Example_VLookupMulti()
JoinMultiKey_VLookup
End Sub
'例2:複合キー+INDEX/MATCH(列増減に強い)
Sub Example_IndexMatchMulti()
JoinMultiKey_IndexMatch
End Sub
'例3:配列+辞書で複合キーJOIN(高速)
Sub Example_DictMulti()
JoinMultiKey_Dictionary
End Sub
'例4:基準×他指標を複数キーで左結合
Sub Example_LeftJoinMulti()
MergeMultiKey_LeftJoin
End Sub
'例5:複数キーでフル結合して完全統合
Sub Example_FullJoinMulti()
MergeMultiKey_FullJoin
End Sub
'例6:見出し名で列特定→複合キーJOIN(列順変更に強い)
Sub Example_ByHeadersMulti()
JoinMultiKey_ByHeaders
End Sub
VB