Excel VBA 逆引き集 | マスタ系重複チェック

Excel VBA
スポンサーリンク

マスタ系重複チェック

「顧客マスタ」「商品マスタ」などの“マスタ系”は、一度重複が混ざると後工程が崩れます。だからこそ、現場で確実に効く“ユニーク制約の代替”と“入力時の予防”をセットで用意しておくのが鉄板。初心者でも貼って動くテンプレを、丁寧にかみ砕いて紹介します。


マスタ重複チェックの設計方針

  • キーを明確化: 何をユニークにするか(例:商品コード、顧客ID、メールアドレス、コード×枝番)。
  • 正規化して判定: 前後空白・大小文字・全角半角・日付書式の揺れを吸収。
  • 2段構え: 入力時にブロック(Worksheet_Change)+定期バッチ検査(辞書+配列)で後追いもカバー。
  • 安全運用: 元表は壊さず一覧出力→承認→“下から削除”の流れに。

共通ユーティリティ(速度・正規化・見出し検索)

Option Explicit

'高速化ON/OFF
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

'テキスト正規化(空白・大小・全半角)
Public Function NormText(ByVal v As Variant) As String
    Dim s As String: s = Trim$(CStr(v))
    s = StrConv(s, vbNarrow) '全角→半角(英数記号)
    NormText = UCase$(s)     '大文字化
End Function

'日付を文字列に統一(yyyy-mm-dd)
Public Function NormDateText(ByVal v As Variant) As String
    On Error Resume Next
    If IsDate(v) Then
        NormDateText = Format$(CDate(v), "yyyy-mm-dd")
    Else
        NormDateText = UCase$(Trim$(CStr(v)))
    End If
    On Error GoTo 0
End Function

'見出し行から列番号を取得(列順に強い)
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 EnsureSheet(ByVal name As String, Optional ByVal clear As Boolean = True) As Worksheet
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = Worksheets(name)
    On Error GoTo 0
    If ws Is Nothing Then
        Set ws = Worksheets.Add(After:=Worksheets(Worksheets.Count))
        ws.Name = name
    End If
    If clear Then ws.Cells.Clear
    Set EnsureSheet = ws
End Function
VB
  • ポイント: マスタは表記揺れが命取り。正規化を必ず通す。列順が変わっても壊れないよう見出し名で列特定。

単一キーの重複チェック(商品コード・顧客IDなど)

「Master」シートの“コード”見出し列をユニークに保てているか、重複一覧を作ります。

Sub MasterCheck_UniqueSingleKey()
    SpeedOn

    Dim ws As Worksheet: Set ws = Worksheets("Master")
    Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
    Dim v As Variant: v = rg.Value

    Dim cKey As Long: cKey = FindHeader(rg.Rows(1), "コード")
    If cKey = 0 Then SpeedOff: MsgBox "見出し 'コード' がありません": Exit Sub

    Dim seen As Object: Set seen = CreateObject("Scripting.Dictionary")
    Dim pos As Object: Set pos = CreateObject("Scripting.Dictionary")

    Dim r As Long, k As String
    For r = 2 To UBound(v, 1)
        k = NormText(v(r, cKey))
        If Len(k) = 0 Then GoTo cont
        If seen.Exists(k) Then
            pos(k) = pos(k) & "," & (rg.Row + r - 1)
        Else
            seen(k) = True
            pos(k) = CStr(rg.Row + r - 1)
        End If
cont:
    Next

    Dim out As Worksheet: Set out = EnsureSheet("重複一覧_単一キー", True)
    out.Range("A1:C1").Value = Array("コード", "出現回数", "行番号一覧")

    Dim i As Long: i = 2
    Dim key As Variant, arr() As String
    For Each key In pos.Keys
        arr = Split(pos(key), ",")
        If UBound(arr) >= 1 Then
            out.Cells(i, 1).Value = key
            out.Cells(i, 2).Value = UBound(arr) + 1
            out.Cells(i, 3).Value = pos(key)
            i = i + 1
        End If
    Next

    out.Columns.AutoFit
    SpeedOff
    MsgBox "単一キーの重複チェック完了。重複キー数: " & i - 2
End Sub
VB
  • 現場の使い方: 一覧を確認→承認後に削除候補を適用(下から削除)。

複合キーの重複チェック(コード×枝番/顧客×住所など)

「コード×枝番」をユニーク制約にしたいときの候補一覧出力。

Sub MasterCheck_UniqueComposite()
    SpeedOn

    Dim ws As Worksheet: Set ws = Worksheets("Master")
    Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
    Dim v As Variant: v = rg.Value

    Dim cCode As Long: cCode = FindHeader(rg.Rows(1), "コード")
    Dim cBranch As Long: cBranch = FindHeader(rg.Rows(1), "枝番")
    If cCode = 0 Or cBranch = 0 Then SpeedOff: MsgBox "見出し 'コード' または '枝番' がありません": Exit Sub

    Dim seen As Object: Set seen = CreateObject("Scripting.Dictionary")
    Dim pos As Object: Set pos = CreateObject("Scripting.Dictionary")

    Dim r As Long, key As String
    For r = 2 To UBound(v, 1)
        key = NormText(v(r, cCode)) & "|" & NormText(v(r, cBranch))
        If InStr(key, "|") = 0 Then GoTo cont
        If seen.Exists(key) Then
            pos(key) = pos(key) & "," & (rg.Row + r - 1)
        Else
            seen(key) = True
            pos(key) = CStr(rg.Row + r - 1)
        End If
cont:
    Next

    Dim out As Worksheet: Set out = EnsureSheet("重複一覧_複合キー", True)
    out.Range("A1:D1").Value = Array("コード", "枝番", "出現回数", "行番号一覧")

    Dim i As Long: i = 2
    Dim k As Variant, parts() As String, arrPos() As String
    For Each k In pos.Keys
        arrPos = Split(pos(k), ",")
        If UBound(arrPos) >= 1 Then
            parts = Split(CStr(k), "|")
            out.Cells(i, 1).Value = parts(0)
            out.Cells(i, 2).Value = parts(1)
            out.Cells(i, 3).Value = UBound(arrPos) + 1
            out.Cells(i, 4).Value = pos(k)
            i = i + 1
        End If
    Next

    out.Columns.AutoFit
    SpeedOff
    MsgBox "複合キーの重複チェック完了。重複組数: " & i - 2
End Sub
VB
  • コツ: 区切りは「|」。値内で使われにくく安全。

入力時の重複予防(シートイベントでブロック)

マスタへの新規入力時に重複を即座に検出・警告。例:「コード」列に同一がある場合に赤塗り&元に戻す。

'監視したいマスタシートのコードモジュール(例:Masterシート)に貼る
Private Sub Worksheet_Change(ByVal Target As Range)
    On Error GoTo fin
    If Target.Columns.Count > 1 Then Exit Sub

    ' 見出しから対象列を特定
    Dim cKeyCol As Long
    cKeyCol = Me.Rows(1).Find(What:="コード", LookAt:=xlWhole, LookIn:=xlValues, MatchCase:=False).Column
    If cKeyCol = 0 Then Exit Sub
    If Target.Column <> cKeyCol Then Exit Sub

    Application.EnableEvents = False

    Dim cell As Range, k As String
    For Each cell In Target.Cells
        k = NormText(cell.Value)
        If Len(k) = 0 Then GoTo cont
        Dim lastRow As Long: lastRow = Me.Cells(Me.Rows.Count, cKeyCol).End(xlUp).Row
        Dim rng As Range: Set rng = Me.Range(Me.Cells(2, cKeyCol), Me.Cells(lastRow, cKeyCol))
        ' CountIfは表記揺れに弱いので「正規化」を活かすため小技:一時列に正規化値を作る版が堅い
        ' ここでは簡易に通常のCountIfでチェック(入力時の粗検知)
        If WorksheetFunction.CountIf(rng, cell.Value) > 1 Then
            cell.Interior.Color = vbRed
            MsgBox "重複コードを検知しました: " & cell.Value
            ' 任意:元に戻す(コメントアウトで通知のみ)
            'cell.Value = ""
        Else
            cell.Interior.ColorIndex = xlNone
        End If
cont:
    Next

fin:
    Application.EnableEvents = True
End Sub
VB
  • 実務の工夫: 正規化した値で厳密判定したい場合、隣列に“正規化コード列”を持ち、その列をCountIf対象にすると精度が上がります。

定期バッチで“ユニーク制約擬似検査”+レポート

夜間や週次で、マスタ全体のユニーク性を一括検査。複数のキーを同時チェックして1枚のレポートにまとめます。

Sub MasterBatch_UniqueReport()
    SpeedOn

    Dim ws As Worksheet: Set ws = Worksheets("Master")
    Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
    Dim v As Variant: v = rg.Value

    Dim colCode As Long: colCode = FindHeader(rg.Rows(1), "コード")
    Dim colName As Long: colName = FindHeader(rg.Rows(1), "名称")
    Dim colMail As Long: colMail = FindHeader(rg.Rows(1), "メール")
    If colCode = 0 Then SpeedOff: MsgBox "見出し 'コード' がありません": Exit Sub

    ' 各キーの辞書(出現→行番号蓄積)
    Dim seenCode As Object: Set seenCode = CreateObject("Scripting.Dictionary")
    Dim posCode As Object: Set posCode = CreateObject("Scripting.Dictionary")
    Dim seenMail As Object: Set seenMail = CreateObject("Scripting.Dictionary")
    Dim posMail As Object: Set posMail = CreateObject("Scripting.Dictionary")

    Dim r As Long, k As String
    For r = 2 To UBound(v, 1)
        ' コード
        k = NormText(v(r, colCode))
        If Len(k) > 0 Then
            If seenCode.Exists(k) Then posCode(k) = posCode(k) & "," & (rg.Row + r - 1) _
            Else seenCode(k) = True: posCode(k) = CStr(rg.Row + r - 1)
        End If
        ' メール(あればチェック)
        If colMail > 0 Then
            k = NormText(v(r, colMail))
            If Len(k) > 0 Then
                If seenMail.Exists(k) Then posMail(k) = posMail(k) & "," & (rg.Row + r - 1) _
                Else seenMail(k) = True: posMail(k) = CStr(rg.Row + r - 1)
            End If
        End If
    Next

    Dim out As Worksheet: Set out = EnsureSheet("マスタ重複レポート", True)
    out.Range("A1:D1").Value = Array("キー種別", "キー値", "出現回数", "行番号一覧")

    Dim i As Long: i = 2
    Dim key As Variant, arr() As String

    ' コード重複
    For Each key In posCode.Keys
        arr = Split(posCode(key), ",")
        If UBound(arr) >= 1 Then
            out.Cells(i, 1).Value = "コード"
            out.Cells(i, 2).Value = key
            out.Cells(i, 3).Value = UBound(arr) + 1
            out.Cells(i, 4).Value = posCode(key)
            i = i + 1
        End If
    Next

    ' メール重複
    For Each key In posMail.Keys
        arr = Split(posMail(key), ",")
        If UBound(arr) >= 1 Then
            out.Cells(i, 1).Value = "メール"
            out.Cells(i, 2).Value = key
            out.Cells(i, 3).Value = UBound(arr) + 1
            out.Cells(i, 4).Value = posMail(key)
            i = i + 1
        End If
    Next

    out.Columns.AutoFit
    SpeedOff
    MsgBox "マスタ重複レポートを作成しました。重複キー総数: " & i - 2
End Sub
VB
  • ポイント: 同一レポートに“コード・メール”など複数キーの重複を並べると、監査や承認が早い。

重複候補の安全削除(承認後・下から)

一覧レポートの行番号を使って、承認済みの重複候補を削除します。

Sub MasterDelete_FromReport()
    SpeedOn

    Dim wsData As Worksheet: Set wsData = Worksheets("Master")
    Dim wsRep As Worksheet: Set wsRep = Worksheets("マスタ重複レポート")
    Dim lastRow As Long: lastRow = wsRep.Cells(wsRep.Rows.Count, "D").End(xlUp).Row
    If lastRow < 2 Then SpeedOff: MsgBox "レポートが空です": Exit Sub

    ' 行番号一覧のうち、2件目以降だけ削除候補にする(先頭は残す)
    Dim dels As Collection: Set dels = New Collection
    Dim r As Long, s As String, parts() As String, i As Long
    For r = 2 To lastRow
        s = CStr(wsRep.Cells(r, "D").Value)
        If Len(s) > 0 Then
            parts = Split(s, ",")
            For i = LBound(parts) + 1 To UBound(parts)
                dels.Add CLng(parts(i))
            Next
        End If
    Next

    ' 下から削除
    Dim x As Long
    For x = dels.Count To 1 Step -1
        wsData.Rows(dels(x)).Delete
    Next

    SpeedOff
    MsgBox "重複候補を削除しました(" & dels.Count & "行、先頭は残す)"
End Sub
VB
  • 鉄則: 上から削除は行ズレの原因。必ず“下から”。

実務向け拡張:別マスタとの重複・衝突(マージ前検査)

「商品マスタ(Master_Product)のコードが、統合予定の別マスタ(Master_Product_New)にも存在する」など、マージ前に衝突を検査。

Sub MasterConflict_BetweenSheets()
    SpeedOn

    Dim wsA As Worksheet: Set wsA = Worksheets("Master_Product")
    Dim wsB As Worksheet: Set wsB = Worksheets("Master_Product_New")
    Dim rgA As Range: Set rgA = wsA.Range("A1").CurrentRegion
    Dim rgB As Range: Set rgB = wsB.Range("A1").CurrentRegion

    Dim cCodeA As Long: cCodeA = FindHeader(rgA.Rows(1), "コード")
    Dim cCodeB As Long: cCodeB = FindHeader(rgB.Rows(1), "コード")
    If cCodeA = 0 Or cCodeB = 0 Then SpeedOff: MsgBox "見出し 'コード' がありません": Exit Sub

    Dim vA As Variant: vA = rgA.Value
    Dim vB As Variant: vB = rgB.Value

    ' B側のコードを辞書化
    Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
    Dim i As Long, k As String
    For i = 2 To UBound(vB, 1)
        k = NormText(vB(i, cCodeB))
        If Len(k) > 0 Then dictB(k) = True
    Next

    ' A側で衝突を抽出
    Dim out As Worksheet: Set out = EnsureSheet("マージ衝突一覧", True)
    out.Range("A1:B1").Value = Array("コード(衝突)", "A側行番号")

    Dim r As Long, o As Long: o = 2
    For r = 2 To UBound(vA, 1)
        k = NormText(vA(r, cCodeA))
        If Len(k) > 0 And dictB.Exists(k) Then
            out.Cells(o, 1).Value = k
            out.Cells(o, 2).Value = rgA.Row + r - 1
            o = o + 1
        End If
    Next

    out.Columns.AutoFit
    SpeedOff
    MsgBox "マージ前のコード衝突を検出しました。件数: " & o - 2
End Sub
VB
  • 使いどころ: マスタ統合前の“衝突”目視リスト。重複の源泉を先に消せます。

例題で練習

'例1:単一キー(コード)の重複一覧
Sub Example_UniqueSingle()
    MasterCheck_UniqueSingleKey
End Sub

'例2:複合キー(コード×枝番)の重複一覧
Sub Example_UniqueComposite()
    MasterCheck_UniqueComposite
End Sub

'例3:入力時の重複予防(コード列で即警告)
' → マスタシートのコードモジュールにWorksheet_Changeを貼る

'例4:定期バッチレポート(コード・メールの重複)
Sub Example_BatchReport()
    MasterBatch_UniqueReport
End Sub

'例5:承認後に重複候補を“下から”削除
Sub Example_DeleteFromReport()
    MasterDelete_FromReport
End Sub

'例6:別マスタとの統合前衝突検査
Sub Example_ConflictCheck()
    MasterConflict_BetweenSheets
End Sub
VB

よくある落とし穴と対策

  • 表記揺れで別物扱い: 正規化(空白・大小・全半角)を必ず通す。必要ならハイフン・記号の統一も。
  • 日付の混在で複合キーがズレる: 比較はDate型化、格納は文字列(yyyy-mm-dd)に統一。
  • 列順・見出し変更: 列番号のハードコード禁止。FindHeaderで見出しから特定。
  • いきなり削除の事故: 候補一覧→承認→“下から削除”の2段階運用。バックアップも習慣化。
  • 大量データで遅い: Range→配列化+Dictionary(今回のテンプレの骨子)。画面・イベント・計算の停止もセット。
タイトルとURLをコピーしました