Excel VBA 逆引き集 | 差分をマーク

Excel VBA
スポンサーリンク

差分をマーク

差分を「抽出」ではなく、元の表に「印を付ける(色・フラグ・コメント)」テンプレです。壊れないコツは、キーの正規化、見出し名で列特定、配列で比較、セル往復は最小に、そして「何が変わったか」をセルに残すこと。


差分の種類とマークの仕方

  • 新規(Bのみ):
    フラグ列に“新規”を立てる、行全体を淡緑で塗る、行頭に「+」マーク。
  • 削除(Aのみ):
    フラグ列に“削除”を立てる、行全体を淡赤で塗る、行頭に「−」マーク。
  • 変更(値が違う):
    変更セルを黄色塗りコメントに「旧→新」を書き、右端に“変更あり”フラグ。

共通ユーティリティ(速度・安全)

Option Explicit

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

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

Private Function NormKey(ByVal v As Variant) As String
    NormKey = UCase$(Trim$(CStr(v)))
End Function
VB
  • 速度: 画面/イベント/計算を止めてから処理→復帰。
  • 見出し参照: 列順が変わっても壊れない。
  • 正規化: Trim+UCaseで一致漏れ防止。

最短テンプレ:AとBを比較してフラグ・色・コメントで差分をマーク

「Aを基準にBと突合」。Aシートにフラグ列を作り、変更セルは黄色、コメントで旧→新を記録。Bのみは別に“新規”フラグで追記します。

Sub MarkDiff_FlagsColorsComments()
    SpeedOn

    'シート構成(例)
    'A: Sheet("A") … コード, 名称, 単価
    'B: Sheet("B") … コード, 名称, 単価
    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 cPriceA As Long: cPriceA = FindHeader(rgA.Rows(1), "単価")
    Dim cKeyB As Long: cKeyB = FindHeader(rgB.Rows(1), "コード")
    Dim cNameB As Long: cNameB = FindHeader(rgB.Rows(1), "名称")
    Dim cPriceB As Long: cPriceB = FindHeader(rgB.Rows(1), "単価")
    If cKeyA * cNameA * cPriceA * cKeyB * cNameB * cPriceB = 0 Then SpeedOff: MsgBox "見出し不足": Exit Sub

    'A側にフラグ列を作成(最後の列の次)
    Dim flagCol As Long: flagCol = rgA.Columns.Count + rgA.Column
    wsA.Cells(1, flagCol).Value = "差分フラグ"

    'B辞書(キー→行番号)
    Dim mapB As Object: Set mapB = CreateObject("Scripting.Dictionary")
    Dim i As Long, k As String
    For i = 2 To UBound(vB, 1)
        k = NormKey(vB(i, cKeyB))
        If Len(k) > 0 Then mapB(k) = i
    Next

    '色の用意
    Dim colorChanged As Long: colorChanged = RGB(255, 235, 156) '黄色
    Dim colorDeleted As Long: colorDeleted = RGB(255, 199, 206) '淡赤

    'A基準で「削除・変更」をマーク
    Dim r As Long
    For r = 2 To UBound(vA, 1)
        k = NormKey(vA(r, cKeyA))
        If Len(k) = 0 Then GoTo contA

        Dim cellName As Range: Set cellName = wsA.Cells(rgA.Row + r - 1, cNameA)
        Dim cellPrice As Range: Set cellPrice = wsA.Cells(rgA.Row + r - 1, cPriceA)
        Dim cellFlag As Range: Set cellFlag = wsA.Cells(rgA.Row + r - 1, flagCol)

        If mapB.Exists(k) Then
            Dim rb As Long: rb = mapB(k)
            '名称比較(文字)
            Dim aName As String: aName = CStr(vA(r, cNameA))
            Dim bName As String: bName = CStr(vB(rb, cNameB))
            If aName <> bName Then
                cellName.Interior.Color = colorChanged
                cellName.AddComment "旧: " & aName & " → 新: " & bName
                cellFlag.Value = AppendFlag(cellFlag.Value, "変更")
            End If
            '単価比較(数値)
            Dim aPrice As Double: aPrice = CDbl(Val(vA(r, cPriceA)))
            Dim bPrice As Double: bPrice = CDbl(Val(vB(rb, cPriceB)))
            If aPrice <> bPrice Then
                cellPrice.Interior.Color = colorChanged
                cellPrice.AddComment "旧: " & aPrice & " → 新: " & bPrice
                cellFlag.Value = AppendFlag(cellFlag.Value, "変更")
            End If
        Else
            'Aのみ=削除
            wsA.Range(wsA.Cells(rgA.Row + r - 1, rgA.Column), _
                      wsA.Cells(rgA.Row + r - 1, rgA.Column + rgA.Columns.Count - 1)).Interior.Color = colorDeleted
            cellFlag.Value = AppendFlag(cellFlag.Value, "削除")
        End If
contA:
    Next

    'Bのみ=新規をB側にフラグ列でマーク(必要ならAへコピーしてマークでも可)
    Dim flagColB As Long: flagColB = rgB.Columns.Count + rgB.Column
    wsB.Cells(1, flagColB).Value = "差分フラグ"
    Dim setA As Object: Set setA = CreateObject("Scripting.Dictionary")
    For r = 2 To UBound(vA, 1)
        k = NormKey(vA(r, cKeyA))
        If Len(k) > 0 Then setA(k) = True
    Next
    Dim colorNew As Long: colorNew = RGB(198, 239, 206) '淡緑
    For i = 2 To UBound(vB, 1)
        k = NormKey(vB(i, cKeyB))
        If Len(k) > 0 And Not setA.Exists(k) Then
            wsB.Range(wsB.Cells(rgB.Row + i - 1, rgB.Column), _
                      wsB.Cells(rgB.Row + i - 1, rgB.Column + rgB.Columns.Count - 1)).Interior.Color = colorNew
            wsB.Cells(rgB.Row + i - 1, flagColB).Value = AppendFlag(wsB.Cells(rgB.Row + i - 1, flagColB).Value, "新規")
        End If
    Next

    '見やすさ
    wsA.Rows(1).Font.Bold = True: wsB.Rows(1).Font.Bold = True
    wsA.Columns.AutoFit: wsB.Columns.AutoFit

    SpeedOff
End Sub

Private Function AppendFlag(ByVal cur As Variant, ByVal add As String) As String
    Dim s As String: s = Trim$(CStr(cur))
    AppendFlag = IIf(s = "", add, s & "," & add)
End Function
VB
  • ポイント
    • 現場で使いやすい痕跡: セルを塗る+コメントで「旧→新」を残す。フラグ列は後で並べ替え・抽出に使える。
    • 比較の型: 数値は Val→CDbl、文字は CStr。型ズレで誤判定しない。

比較項目を増減できる柔軟テンプレ(見出し名で指定)

「名称・カテゴリ・単価・ステータス…」など任意項目を配列で指定。変更セルを色付け・コメントでマーク。

Sub MarkDiff_FlexibleFields()
    SpeedOn

    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 cKeyB As Long: cKeyB = FindHeader(rgB.Rows(1), "コード")
    If cKeyA = 0 Or cKeyB = 0 Then SpeedOff: MsgBox "キー見出し不足": Exit Sub

    '比較項目の見出し名
    Dim fields As Variant: fields = Array("名称", "カテゴリ", "単価", "ステータス")

    '項目→列番号のマップ
    Dim i As Long
    Dim mapA() As Long: ReDim mapA(LBound(fields) To UBound(fields))
    Dim mapB() As Long: ReDim mapB(LBound(fields) To UBound(fields))
    For i = LBound(fields) To UBound(fields)
        mapA(i) = FindHeader(rgA.Rows(1), fields(i))
        mapB(i) = FindHeader(rgB.Rows(1), fields(i))
        If mapA(i) = 0 Or mapB(i) = 0 Then SpeedOff: MsgBox "見出し不足:" & fields(i): Exit Sub
    Next

    'B辞書(キー→行番号)
    Dim mapBRow As Object: Set mapBRow = CreateObject("Scripting.Dictionary")
    Dim r As Long, k As String
    For r = 2 To UBound(vB, 1)
        k = NormKey(vB(r, cKeyB))
        If Len(k) > 0 Then mapBRow(k) = r
    Next

    'フラグ列
    Dim flagCol As Long: flagCol = rgA.Columns.Count + rgA.Column
    wsA.Cells(1, flagCol).Value = "差分フラグ"

    Dim colorChanged As Long: colorChanged = RGB(255, 235, 156)

    'Aループ:変更セルをマーク
    For r = 2 To UBound(vA, 1)
        k = NormKey(vA(r, cKeyA))
        If Len(k) = 0 Or Not mapBRow.Exists(k) Then GoTo contA
        Dim rb As Long: rb = mapBRow(k)

        Dim changed As Boolean: changed = False
        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 isNum As Boolean: isNum = (fields(i) Like "*単価*" Or fields(i) Like "*金額*")
            Dim diff As Boolean
            If isNum Then
                diff = (CDbl(Val(va)) <> CDbl(Val(vb)))
            Else
                diff = (CStr(va) <> CStr(vb))
            End If

            If diff Then
                Dim cellA As Range: Set cellA = wsA.Cells(rgA.Row + r - 1, mapA(i))
                cellA.Interior.Color = colorChanged
                cellA.AddComment fields(i) & " 旧: " & va & " → 新: " & vb
                changed = True
            End If
        Next

        If changed Then
            wsA.Cells(rgA.Row + r - 1, flagCol).Value = AppendFlag(wsA.Cells(rgA.Row + r - 1, flagCol).Value, "変更")
        End If
contA:
    Next

    wsA.Rows(1).Font.Bold = True
    wsA.Columns.AutoFit
    SpeedOff
End Sub
VB
  • ポイント
    • 数値項目は数値比較: フォーマットではなく中身で比較。
    • コメントの項目名明記: どの項目が変わったか一目で分かる。

複合キー(例:コード×年月)の差分マーク

年月でバージョンが変わる場合などに。キーを「コード|yyyy-mm」に連結して比較。

Private Function BuildKey2(ByVal code As Variant, ByVal ymd As Variant) As String
    Dim ym As String
    If IsDate(ymd) Then ym = Format$(CDate(ymd), "yyyy-mm") Else ym = CStr(ymd)
    BuildKey2 = NormKey(code) & "|" & UCase$(Trim$(ym))
End Function

Sub MarkDiff_MultiKey()
    SpeedOn

    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

    '列想定:A=コード, B=年月, C=名称, D=単価(必要ならFindHeaderで置換)
    Dim colorChanged As Long: colorChanged = RGB(255, 235, 156)
    Dim flagColA As Long: flagColA = rgA.Columns.Count + rgA.Column
    Dim flagColB As Long: flagColB = rgB.Columns.Count + rgB.Column
    wsA.Cells(1, flagColA).Value = "差分フラグ"
    wsB.Cells(1, flagColB).Value = "差分フラグ"

    'B辞書(複合キー→行)
    Dim mapB As Object: Set mapB = CreateObject("Scripting.Dictionary")
    Dim i As Long, key As String
    For i = 2 To UBound(vB, 1)
        key = BuildKey2(vB(i, 1), vB(i, 2))
        mapB(key) = i
    Next

    'Aループ:削除・変更
    For i = 2 To UBound(vA, 1)
        key = BuildKey2(vA(i, 1), vA(i, 2))
        Dim rowA As Long: rowA = rgA.Row + i - 1
        If mapB.Exists(key) Then
            Dim rb As Long: rb = mapB(key)
            '名称
            If CStr(vA(i, 3)) <> CStr(vB(rb, 3)) Then
                wsA.Cells(rowA, rgA.Column + 2).Interior.Color = colorChanged
                wsA.Cells(rowA, rgA.Column + 2).AddComment "旧: " & vA(i, 3) & " → 新: " & vB(rb, 3)
                wsA.Cells(rowA, flagColA).Value = AppendFlag(wsA.Cells(rowA, flagColA).Value, "変更")
            End If
            '単価
            If CDbl(Val(vA(i, 4))) <> CDbl(Val(vB(rb, 4))) Then
                wsA.Cells(rowA, rgA.Column + 3).Interior.Color = colorChanged
                wsA.Cells(rowA, rgA.Column + 3).AddComment "旧: " & vA(i, 4) & " → 新: " & vB(rb, 4)
                wsA.Cells(rowA, flagColA).Value = AppendFlag(wsA.Cells(rowA, flagColA).Value, "変更")
            End If
        Else
            'Aのみ=削除
            wsA.Rows(rowA).Interior.Color = RGB(255, 199, 206)
            wsA.Cells(rowA, flagColA).Value = AppendFlag(wsA.Cells(rowA, flagColA).Value, "削除")
        End If
    Next

    'Bのみ=新規
    Dim setA As Object: Set setA = CreateObject("Scripting.Dictionary")
    For i = 2 To UBound(vA, 1)
        setA(BuildKey2(vA(i, 1), vA(i, 2))) = True
    Next
    For i = 2 To UBound(vB, 1)
        key = BuildKey2(vB(i, 1), vB(i, 2))
        If Not setA.Exists(key) Then
            Dim rowB As Long: rowB = rgB.Row + i - 1
            wsB.Rows(rowB).Interior.Color = RGB(198, 239, 206)
            wsB.Cells(rowB, flagColB).Value = AppendFlag(wsB.Cells(rowB, flagColB).Value, "新規")
        End If
    Next

    wsA.Columns.AutoFit: wsB.Columns.AutoFit
    SpeedOff
End Sub
VB
  • ポイント
    • 年月を yyyy-mm 統一: 揺れを防ぐ。
    • 安全区切り: 複合キーは「|」連結が無難。

実務の落とし穴と対策

  • コメントが大量で重くなる
    • 対策: コメントの代わりに「隣列に“旧→新”テキスト」を書く運用も可。監査後にコメント削除ルーチンを用意。
  • 塗り色が既存の色とぶつかる
    • 対策: 差分用の色パレットを決める。塗り前に既存色を退避したい場合は別列に色コード保存。
  • 型ズレで誤判定
    • 対策: 数値は Val→CDbl、日付は IsDate→Format、文字は CStr。比較の型ルールを固定。
  • 列順の変更で壊れる
    • 対策: 可能なら FindHeader を使い、見出しから列位置を特定。

例題で練習

'例1:基本(フラグ+色+コメントで差分マーク)
Sub Example_MarkBasic()
    MarkDiff_FlagsColorsComments
End Sub

'例2:見出し名で比較項目を柔軟に指定して差分マーク
Sub Example_MarkFlexible()
    MarkDiff_FlexibleFields
End Sub

'例3:複合キー(コード×年月)の差分マーク
Sub Example_MarkMultiKey()
    MarkDiff_MultiKey
End Sub
VB

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