A/Bマスタの突合
「AマスタとBマスタを突き合わせて、一致・不一致・差分(名称や属性の違い)を洗い出したい」——初心者でも壊れず、現場でそのまま使えるテンプレをまとめました。肝は「キーの正規化」「見出し名で列特定」「配列+辞書で高速」「未一致・差分を“見える化”」です。
突合の設計指針
- キーの決定: コードなどの主キーを必ず一つに定め、複数キーは安全な区切りで連結(例:”コード|年月”)。
- 正規化の徹底: キーは Trim/UCase(必要なら半角化)で表記揺れを吸収。
- 列順変動に耐える: 見出し名で列位置を取得して参照(ハードコードしない)。
- 突合の出力粒度: 1) 完全一致、2) 片側のみ、3) 値が異なる(項目別)に分けて可視化。
- 速度最適化: 範囲→配列→辞書→一括貼付。セルを行き来しない。
下準備:見出し特定と新シートユーティリティ
Option Explicit
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
Public 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
VB- ポイント
- FindHeader: 見出し名で列位置を取得して壊れにくく。
- EnsureSheet: 何度実行しても安全に再利用/作成。
単一キーの突合:一致・未登録・差分の3分類
AマスタとBマスタを「コード」キーで突合し、3分類を別シートに出力します。
Sub ReconcileMasters_Simple()
'A: Sheet("Aマスタ") A=コード, B=名称, C=カテゴリ
'B: Sheet("Bマスタ") A=コード, B=名称, C=カテゴリ
Dim wsA As Worksheet: Set wsA = Worksheets("Aマスタ")
Dim wsB As Worksheet: Set wsB = Worksheets("Bマスタ")
Dim rgA As Range: Set rgA = wsA.Range("A1").CurrentRegion
Dim rgB As Range: Set rgB = wsB.Range("A1").CurrentRegion
Dim vA As Variant: vA = rgA.Value
Dim vB As Variant: vB = rgB.Value
'見出し列特定
Dim cKeyA As Long: cKeyA = FindHeader(rgA.Rows(1), "コード")
Dim cNameA As Long: cNameA = FindHeader(rgA.Rows(1), "名称")
Dim cCatA As Long: cCatA = FindHeader(rgA.Rows(1), "カテゴリ")
Dim cKeyB As Long: cKeyB = FindHeader(rgB.Rows(1), "コード")
Dim cNameB As Long: cNameB = FindHeader(rgB.Rows(1), "名称")
Dim cCatB As Long: cCatB = FindHeader(rgB.Rows(1), "カテゴリ")
If cKeyA*cNameA*cCatA*cKeyB*cNameB*cCatB = 0 Then MsgBox "見出し不足": Exit Sub
'B辞書(キー→(名称,カテゴリ))
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim i As Long, key As String
For i = 2 To UBound(vB, 1)
key = UCase$(Trim$(CStr(vB(i, cKeyB))))
If Len(key) > 0 Then dictB(key) = Array(CStr(vB(i, cNameB)), CStr(vB(i, cCatB)))
Next
'出力シート
Dim wsOK As Worksheet: Set wsOK = EnsureSheet("一致", True)
Dim wsMissA As Worksheet: Set wsMissA = EnsureSheet("Bに未登録", True)
Dim wsMissB As Worksheet: Set wsMissB = EnsureSheet("Aに未登録", True)
Dim wsDiff As Worksheet: Set wsDiff = EnsureSheet("差分", True)
wsOK.Range("A1:D1").Value = Array("コード", "名称(A)", "名称(B)", "カテゴリ一致")
wsMissA.Range("A1:C1").Value = Array("コード", "名称(A)", "カテゴリ(A)")
wsMissB.Range("A1:C1").Value = Array("コード", "名称(B)", "カテゴリ(B)")
wsDiff.Range("A1:E1").Value = Array("コード", "項目", "A値", "B値", "備考")
'Aを走査:一致/差分/未登録(B)
Dim rOK As Long: rOK = 2
Dim rDiff As Long: rDiff = 2
Dim rMissA As Long: rMissA = 2
For i = 2 To UBound(vA, 1)
key = UCase$(Trim$(CStr(vA(i, cKeyA))))
If Len(key) = 0 Then GoTo nextA
Dim aName As String: aName = CStr(vA(i, cNameA))
Dim aCat As String: aCat = CStr(vA(i, cCatA))
If dictB.Exists(key) Then
Dim bName As String: bName = dictB(key)(0)
Dim bCat As String: bCat = dictB(key)(1)
If aName = bName And aCat = bCat Then
wsOK.Cells(rOK, 1).Value = key
wsOK.Cells(rOK, 2).Value = aName
wsOK.Cells(rOK, 3).Value = bName
wsOK.Cells(rOK, 4).Value = IIf(aCat = bCat, "一致", "不一致") '名称一致ならカテゴリも見ておく
rOK = rOK + 1
Else
If aName <> bName Then
wsDiff.Cells(rDiff, 1).Value = key
wsDiff.Cells(rDiff, 2).Value = "名称"
wsDiff.Cells(rDiff, 3).Value = aName
wsDiff.Cells(rDiff, 4).Value = bName
wsDiff.Cells(rDiff, 5).Value = ""
rDiff = rDiff + 1
End If
If aCat <> bCat Then
wsDiff.Cells(rDiff, 1).Value = key
wsDiff.Cells(rDiff, 2).Value = "カテゴリ"
wsDiff.Cells(rDiff, 3).Value = aCat
wsDiff.Cells(rDiff, 4).Value = bCat
wsDiff.Cells(rDiff, 5).Value = ""
rDiff = rDiff + 1
End If
End If
Else
wsMissA.Cells(rMissA, 1).Value = key
wsMissA.Cells(rMissA, 2).Value = aName
wsMissA.Cells(rMissA, 3).Value = aCat
rMissA = rMissA + 1
End If
nextA:
Next
'Bのみ(Aに未登録)を出力
Dim presentA As Object: Set presentA = CreateObject("Scripting.Dictionary")
For i = 2 To UBound(vA, 1)
key = UCase$(Trim$(CStr(vA(i, cKeyA))))
If Len(key) > 0 Then presentA(key) = True
Next
Dim rMissB As Long: rMissB = 2
Dim j As Long
For j = 2 To UBound(vB, 1)
key = UCase$(Trim$(CStr(vB(j, cKeyB))))
If Len(key) > 0 And Not presentA.Exists(key) Then
wsMissB.Cells(rMissB, 1).Value = key
wsMissB.Cells(rMissB, 2).Value = CStr(vB(j, cNameB))
wsMissB.Cells(rMissB, 3).Value = CStr(vB(j, cCatB))
rMissB = rMissB + 1
End If
Next
wsOK.Columns.AutoFit: wsDiff.Columns.AutoFit
wsMissA.Columns.AutoFit: wsMissB.Columns.AutoFit
End Sub
VB- ポイント
- 3分類出力: 一致・差分・片側未登録を一目で把握。
- 項目別差分: 1差分=1行にすると修正方針が立てやすい。
比較項目を柔軟に増減(配列指定)
「名称・カテゴリ・単価・ステータスなど、比較項目が増える」ケースに対応します。
Sub ReconcileMasters_Flexible()
'A/Bどちらも「コード」キー。比較項目を配列で指定
Dim vA As Variant: vA = Worksheets("Aマスタ").Range("A1").CurrentRegion.Value
Dim vB As Variant: vB = Worksheets("Bマスタ").Range("A1").CurrentRegion.Value
Dim cKeyA As Long: cKeyA = FindHeader(Worksheets("Aマスタ").Range("A1").CurrentRegion.Rows(1), "コード")
Dim cKeyB As Long: cKeyB = FindHeader(Worksheets("Bマスタ").Range("A1").CurrentRegion.Rows(1), "コード")
If cKeyA * cKeyB = 0 Then MsgBox "キー見出し不足": Exit Sub
'比較したい項目(見出し名)
Dim fields As Variant: fields = Array("名称", "カテゴリ", "単価", "ステータス")
'A/B項目→列番号
Dim mapA() As Long: ReDim mapA(LBound(fields) To UBound(fields))
Dim mapB() As Long: ReDim mapB(LBound(fields) To UBound(fields))
Dim i As Long
For i = LBound(fields) To UBound(fields)
mapA(i) = FindHeader(Worksheets("Aマスタ").Range("A1").CurrentRegion.Rows(1), CStr(fields(i)))
mapB(i) = FindHeader(Worksheets("Bマスタ").Range("A1").CurrentRegion.Rows(1), CStr(fields(i)))
If mapA(i) = 0 Or mapB(i) = 0 Then MsgBox "見出し不足:" & fields(i): Exit Sub
Next
'B辞書(キー→行番号)
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim k As String, r As Long
For r = 2 To UBound(vB, 1)
k = UCase$(Trim$(CStr(vB(r, cKeyB))))
If Len(k) > 0 Then dictB(k) = r
Next
'出力
Dim wsDiff As Worksheet: Set wsDiff = EnsureSheet("差分_柔軟", True)
wsDiff.Range("A1:D1").Value = Array("コード", "項目", "A値", "B値")
Dim rOut As Long: rOut = 2
'Aを走査(差分のみ出す)
For r = 2 To UBound(vA, 1)
k = UCase$(Trim$(CStr(vA(r, cKeyA))))
If Len(k) = 0 Then GoTo contA
If dictB.Exists(k) Then
Dim rb As Long: rb = dictB(k)
For i = LBound(fields) To UBound(fields)
Dim va As Variant: va = vA(r, mapA(i))
Dim vb As Variant: vb = vB(rb, mapB(i))
Dim isDiff As Boolean
If fields(i) Like "*単価*" Or fields(i) Like "*金額*" Then
isDiff = (CDbl(Val(va)) <> CDbl(Val(vb)))
Else
isDiff = (CStr(va) <> CStr(vb))
End If
If isDiff Then
wsDiff.Cells(rOut, 1).Value = k
wsDiff.Cells(rOut, 2).Value = fields(i)
wsDiff.Cells(rOut, 3).Value = va
wsDiff.Cells(rOut, 4).Value = vb
rOut = rOut + 1
End If
Next
End If
contA:
Next
wsDiff.Columns.AutoFit
End Sub
VB- ポイント
- 項目配列: 比較対象を簡単に増減できる。
- 数値項目は数値比較: 文字列化による比較ズレを防ぐ。
複数キーの突合(例:コード×年月)
部署や年月などを含めた複合キーで突合するテンプレです。
Sub ReconcileMasters_MultiKey()
'A: A=コード, B=年月, C=名称
'B: A=コード, B=年月, C=名称B
Dim rgA As Range: Set rgA = Worksheets("Aマスタ").Range("A1").CurrentRegion
Dim rgB As Range: Set rgB = Worksheets("Bマスタ").Range("A1").CurrentRegion
Dim vA As Variant: vA = rgA.Value
Dim vB As Variant: vB = rgB.Value
'見出し
Dim cCodeA As Long: cCodeA = FindHeader(rgA.Rows(1), "コード")
Dim cYmA As Long: cYmA = FindHeader(rgA.Rows(1), "年月")
Dim cNameA As Long: cNameA = FindHeader(rgA.Rows(1), "名称")
Dim cCodeB As Long: cCodeB = FindHeader(rgB.Rows(1), "コード")
Dim cYmB As Long: cYmB = FindHeader(rgB.Rows(1), "年月")
Dim cNameB As Long: cNameB = FindHeader(rgB.Rows(1), "名称B")
If cCodeA*cYmA*cNameA*cCodeB*cYmB*cNameB = 0 Then MsgBox "見出し不足": Exit Sub
'B辞書(key="コード|yyyy-mm" → 名称B)
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim i As Long, key As String
For i = 2 To UBound(vB, 1)
Dim ymB As String
ymB = IIf(IsDate(vB(i, cYmB)), Format$(CDate(vB(i, cYmB)), "yyyy-mm"), CStr(vB(i, cYmB)))
key = UCase$(Trim$(CStr(vB(i, cCodeB)))) & "|" & UCase$(Trim$(ymB))
dictB(key) = CStr(vB(i, cNameB))
Next
'出力
Dim wsDiff As Worksheet: Set wsDiff = EnsureSheet("差分_複数キー", True)
wsDiff.Range("A1:D1").Value = Array("コード", "年月", "名称(A)", "名称(B)")
Dim rOut As Long: rOut = 2
For i = 2 To UBound(vA, 1)
Dim ymA As String
ymA = IIf(IsDate(vA(i, cYmA)), Format$(CDate(vA(i, cYmA)), "yyyy-mm"), CStr(vA(i, cYmA)))
key = UCase$(Trim$(CStr(vA(i, cCodeA)))) & "|" & UCase$(Trim$(ymA))
Dim aName As String: aName = CStr(vA(i, cNameA))
Dim bName As String: bName = IIf(dictB.Exists(key), dictB(key), "")
If bName = "" Or aName <> bName Then
wsDiff.Cells(rOut, 1).Value = vA(i, cCodeA)
wsDiff.Cells(rOut, 2).Value = ymA
wsDiff.Cells(rOut, 3).Value = aName
wsDiff.Cells(rOut, 4).Value = bName
rOut = rOut + 1
End If
Next
wsDiff.Columns.AutoFit
End Sub
VB- ポイント
- 年月統一: Date型なら
Format$("yyyy-mm")で表記統一。 - 安全な区切り:
"|"のようなデータに出てこない記号を使う。
- 年月統一: Date型なら
和集合・共通集合で全体像を可視化(完全・内部)
「両側にあるキー(内部)」「片側だけのキー(差分)」を一覧化します。
Sub ReconcileMasters_Sets()
Dim vA As Variant: vA = Worksheets("Aマスタ").Range("A1").CurrentRegion.Value
Dim vB As Variant: vB = Worksheets("Bマスタ").Range("A1").CurrentRegion.Value
Dim cKeyA As Long: cKeyA = FindHeader(Worksheets("Aマスタ").Range("A1").CurrentRegion.Rows(1), "コード")
Dim cKeyB As Long: cKeyB = FindHeader(Worksheets("Bマスタ").Range("A1").CurrentRegion.Rows(1), "コード")
If cKeyA * cKeyB = 0 Then MsgBox "見出し不足": Exit Sub
Dim setA As Object: Set setA = CreateObject("Scripting.Dictionary")
Dim setB As Object: Set setB = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vA, 1)
k = UCase$(Trim$(CStr(vA(i, cKeyA))))
If Len(k) > 0 Then setA(k) = True
Next
For i = 2 To UBound(vB, 1)
k = UCase$(Trim$(CStr(vB(i, cKeyB))))
If Len(k) > 0 Then setB(k) = True
Next
Dim ws As Worksheet: Set ws = EnsureSheet("集合比較", True)
ws.Range("A1:C1").Value = Array("キー", "分類", "備考")
Dim rOut As Long: rOut = 2
Dim all As Object: Set all = CreateObject("Scripting.Dictionary")
For Each k In setA.Keys: all(k) = True: Next
For Each k In setB.Keys: all(k) = True: Next
Dim key As Variant
For Each key In all.Keys
ws.Cells(rOut, 1).Value = key
If setA.Exists(key) And setB.Exists(key) Then
ws.Cells(rOut, 2).Value = "共通(内部)"
ElseIf setA.Exists(key) Then
ws.Cells(rOut, 2).Value = "Aのみ"
Else
ws.Cells(rOut, 2).Value = "Bのみ"
End If
rOut = rOut + 1
Next
ws.Columns.AutoFit
End Sub
VB- ポイント
- 集合の発想: 突合の全体像を「共通/片側のみ」で整理。
- 監査の起点: まず集合で全体把握→差分詳細へ。
監査の見える化(重複・未一致・件数)
「B辞書化時に重複発生」「突合の未一致キー」を同時に出力して監査強化します。
Sub ReconcileMasters_WithAudit()
Dim vA As Variant: vA = Worksheets("Aマスタ").Range("A1").CurrentRegion.Value
Dim vB As Variant: vB = Worksheets("Bマスタ").Range("A1").CurrentRegion.Value
Dim cKeyA As Long: cKeyA = FindHeader(Worksheets("Aマスタ").Range("A1").CurrentRegion.Rows(1), "コード")
Dim cKeyB As Long: cKeyB = FindHeader(Worksheets("Bマスタ").Range("A1").CurrentRegion.Rows(1), "コード")
Dim cNameA As Long: cNameA = FindHeader(Worksheets("Aマスタ").Range("A1").CurrentRegion.Rows(1), "名称")
Dim cNameB As Long: cNameB = FindHeader(Worksheets("Bマスタ").Range("A1").CurrentRegion.Rows(1), "名称")
If cKeyA*cKeyB*cNameA*cNameB = 0 Then MsgBox "見出し不足": Exit Sub
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim dupB As Object: Set dupB = CreateObject("Scripting.Dictionary")
Dim i As Long, key As String
For i = 2 To UBound(vB, 1)
key = UCase$(Trim$(CStr(vB(i, cKeyB))))
If dictB.Exists(key) Then
dupB(key) = True
Else
dictB(key) = CStr(vB(i, cNameB))
End If
Next
Dim miss As Object: Set miss = CreateObject("Scripting.Dictionary")
For i = 2 To UBound(vA, 1)
key = UCase$(Trim$(CStr(vA(i, cKeyA))))
If Len(key) > 0 And Not dictB.Exists(key) Then miss(key) = True
Next
Dim wsLog As Worksheet: Set wsLog = EnsureSheet("突合監査", True)
wsLog.Range("A1:B1").Value = Array("AにあってBにないキー", "B側重複キー")
Dim r1 As Long: r1 = 2
Dim x As Variant
For Each x In miss.Keys: wsLog.Cells(r1, 1).Value = x: r1 = r1 + 1: Next
Dim r2 As Long: r2 = 2
For Each x In dupB.Keys: wsLog.Cells(r2, 2).Value = x: r2 = r2 + 1: Next
wsLog.Columns.AutoFit
End Sub
VB- ポイント
- 重複の即可視化: マスタ側の品質問題を早期に把握。
- 未一致一覧: 修正・登録漏れの起票に直結。
よくある落とし穴と対策
- キー表記揺れで一致判定が崩れる
- 対策: 正規化: Trim/UCase、必要なら半角化(StrConv)や記号除去(Replace)。
- 日付・年月の型が混在
- 対策: 統一表記: Date型は
Format$("yyyy-mm")へ変換して比較。
- 対策: 統一表記: Date型は
- 数値を文字列で比較して誤判定
- 対策: 数値化:
Val→CDblで比較。書式は出力時に整える。
- 対策: 数値化:
- 列順変更で壊れる
- 対策: 見出し名参照: FindHeaderで列位置取得。ハードコード禁止。
- 大規模で遅い
- 対策: 配列+辞書+一括貼付。前後で
ScreenUpdating=Falseと計算停止で安定。
- 対策: 配列+辞書+一括貼付。前後で
例題で練習
'例1:単一キーの3分類突合
Sub Example_ReconcileSimple()
ReconcileMasters_Simple
End Sub
'例2:比較項目を柔軟指定して差分一覧
Sub Example_ReconcileFlexible()
ReconcileMasters_Flexible
End Sub
'例3:複数キー(コード×年月)の突合
Sub Example_ReconcileMultiKey()
ReconcileMasters_MultiKey
End Sub
'例4:集合比較で全体像を可視化
Sub Example_ReconcileSets()
ReconcileMasters_Sets
End Sub
'例5:監査ログ(未一致・重複)を出力
Sub Example_ReconcileAudit()
ReconcileMasters_WithAudit
End Sub
VB