顧客コードや伝票番号に重複がないかを確かめたいとき、いきなり削除してしまうと、どの行が消えたのか、本当に同じデータだったのかを後から確認できません。まずは元のデータを1行も消さずに、重複している行に印を付け、どのキーが何件、何行目にあるかを一覧にします。この記事では、そこまでをマクロで行う方法を紹介します。
このマクロでできること
例として、次のような「顧客一覧」シートがあるとします。1行目が見出しで、B列が顧客コードです。
| 行 | A:氏名 | B:顧客コード | … | H:重複チェック |
|---|---|---|---|---|
| 2 | 山田 | C001 | … | |
| 3 | 佐藤 | C002 | … | 重複 1/2 |
| 4 | 鈴木 | C003 | … | |
| 5 | 佐藤 | C002 | … | 重複 2/2 |
| 6 | 田中 | … | キー空白 |
マクロを実行すると、次の3つを出力します。
- 判定列(この例ではH列)に「重複 1/2」「重複 2/2」のような印を書き込み、そのセルに色を付ける
- 「重複一覧」シートに、重複しているキー・件数・行番号の一覧を作る
- データ行数、重複しているキーの種類数、重複に含まれる行数をメッセージで表示する
マクロが書き込むのは、判定列と「重複一覧」シートだけです。元のデータのセルの値や色は変更しません。
コード
標準モジュールに貼り付けます。先頭の定数とシート名は、お使いの表に合わせて変更してください。
Option Explicit
Public Sub CheckDuplicates()
Const SHEET_NAME As String = "顧客一覧"
Const KEY_COL As Long = 2 ' キー列(B列)
Const FLAG_COL As Long = 8 ' 判定を書き込む列(H列)
Const HEADER_ROW As Long = 1 ' 見出し行
Const FLAG_HEADER As String = "重複チェック"
Dim ws As Worksheet, outWs As Worksheet
Dim cnt As Object, rowsOf As Object, seen As Object
Dim keys As Variant, flags() As Variant, key As Variant
Dim firstRow As Long, lastRow As Long, n As Long
Dim i As Long, r As Long
Dim k As String
Dim dupKeys As Long, dupRows As Long
Set ws = ThisWorkbook.Worksheets(SHEET_NAME)
firstRow = HEADER_ROW + 1
lastRow = ws.Cells(ws.Rows.Count, KEY_COL).End(xlUp).Row
If lastRow < firstRow Then
MsgBox "データがありません。", vbExclamation
Exit Sub
End If
n = lastRow - firstRow + 1
' 判定列に別のデータがあれば止める(前回の判定結果なら上書きする)
If Application.WorksheetFunction.CountA(ws.Columns(FLAG_COL)) > 0 Then
If ws.Cells(HEADER_ROW, FLAG_COL).Value <> FLAG_HEADER Then
MsgBox "判定列にデータがあります。FLAG_COL を空いている列に変えてください。", vbExclamation
Exit Sub
End If
End If
' キー列をまとめて配列に読み込む
If n = 1 Then
ReDim keys(1 To 1, 1 To 1)
keys(1, 1) = ws.Cells(firstRow, KEY_COL).Value
Else
keys = ws.Range(ws.Cells(firstRow, KEY_COL), ws.Cells(lastRow, KEY_COL)).Value
End If
Set cnt = CreateObject("Scripting.Dictionary")
Set rowsOf = CreateObject("Scripting.Dictionary")
Set seen = CreateObject("Scripting.Dictionary")
cnt.CompareMode = vbTextCompare ' 大文字・小文字を同じキーとして扱う
rowsOf.CompareMode = vbTextCompare
seen.CompareMode = vbTextCompare
' 1回目:キーごとの件数と行番号を集める
For i = 1 To n
If Not IsError(keys(i, 1)) Then
k = Trim$(CStr(keys(i, 1)))
If Len(k) > 0 Then
r = firstRow + i - 1
If cnt.Exists(k) Then
cnt(k) = cnt(k) + 1
rowsOf(k) = rowsOf(k) & ", " & r
Else
cnt.Add k, 1
rowsOf.Add k, CStr(r)
End If
End If
End If
Next i
' 2回目:行ごとの判定を作る
ReDim flags(1 To n, 1 To 1)
For i = 1 To n
If IsError(keys(i, 1)) Then
flags(i, 1) = "キーがエラー値"
Else
k = Trim$(CStr(keys(i, 1)))
If Len(k) = 0 Then
flags(i, 1) = "キー空白"
ElseIf cnt(k) >= 2 Then
If seen.Exists(k) Then
seen(k) = seen(k) + 1
Else
seen.Add k, 1
End If
flags(i, 1) = "重複 " & seen(k) & "/" & cnt(k)
dupRows = dupRows + 1
Else
flags(i, 1) = ""
End If
End If
Next i
' 判定列を書き直す(判定列以外には書き込まない)
ws.Columns(FLAG_COL).Clear
ws.Cells(HEADER_ROW, FLAG_COL).Value = FLAG_HEADER
ws.Cells(firstRow, FLAG_COL).Resize(n, 1).Value = flags
For i = 1 To n
If Left$(flags(i, 1), 2) = "重複" Then
ws.Cells(firstRow + i - 1, FLAG_COL).Interior.Color = RGB(255, 235, 156)
End If
Next i
' 重複一覧シートを用意する
On Error Resume Next
Set outWs = ThisWorkbook.Worksheets("重複一覧")
On Error GoTo 0
If outWs Is Nothing Then
Set outWs = ThisWorkbook.Worksheets.Add(After:=ws)
outWs.Name = "重複一覧"
Else
outWs.Cells.Clear
End If
outWs.Columns(1).NumberFormat = "@" ' 00123 のようなキーを文字列のまま書く
outWs.Columns(3).NumberFormat = "@"
outWs.Range("A1:C1").Value = Array("キー", "件数", "行番号")
r = 2
For Each key In cnt.Keys
If cnt(key) >= 2 Then
outWs.Cells(r, 1).Value = key
outWs.Cells(r, 2).Value = cnt(key)
outWs.Cells(r, 3).Value = rowsOf(key)
r = r + 1
dupKeys = dupKeys + 1
End If
Next key
outWs.Columns("A:C").AutoFit
MsgBox "データ行数: " & n & " 行" & vbCrLf & _
"重複しているキー: " & dupKeys & " 種類" & vbCrLf & _
"重複に含まれる行: " & dupRows & " 行", vbInformation
End Sub
実行前に確認すること
- 判定列は空いている列にする:判定列は毎回いったん消去してから書き直します。見出しが「重複チェック」以外の値が入っている列を指定した場合は、何も書き込まずに止まるようにしています。
- 「重複一覧」という名前のシートを別の用途で使っていないか:同じ名前のシートがあると、その中身を消去してから一覧を書き込みます。
- 最終行を判定する列:最終行はキー列の下から探しています。キー列の末尾に空欄がある場合は、その行が判定の対象から外れます。必ず値が入る列で
lastRowを取るように変更してください。 - フィルターは解除しておく:絞り込んだ状態だと、最終行が正しく取れないことがあります。
- 件数が多い場合:数万行以上あると、色付けのループに時間がかかります。処理の先頭で
Application.ScreenUpdating = Falseを指定し、最後にTrueに戻すと速くなります。
何を「同じキー」とみなすか
重複チェックの結果は、キーをどう比べるかで変わります。このコードの扱いと、変えたい場合の直し方は次のとおりです。
| 違いの例 | このコードでの扱い | 変えたい場合 |
|---|---|---|
前後の半角スペース(C002 と C002 ) |
同じキー(Trim$ で除いている) |
区別したいなら Trim$ を外す |
| 全角スペース | Trim$ では除かれない |
Replace(k, " ", "") を追加する |
大文字と小文字(c002 と C002) |
同じキー(vbTextCompare) |
区別したいなら CompareMode の3行を消す |
数値の 123 と文字列の "123" |
同じキー(CStr で文字列にしてから比べる) |
区別したいなら TypeName(keys(i, 1)) をキーに含める |
123 と 00123 |
別のキー | 桁をそろえたいなら Format で整形してから比べる |
| 全角と半角の英数字 | 比較方法によって変わる(下記) | StrConv(k, vbNarrow) で半角にそろえる(カタカナも半角になる点に注意) |
vbTextCompare で比較すると、日本語環境では大文字・小文字に加えて、全角・半角やひらがな・カタカナの違いも同じとみなされる場合があります。意図どおりに判定されるか、数行の見本データで先に確かめてください。比較方法を変える場合は、cnt・rowsOf・seen の3つの CompareMode を必ずそろえます。また、CompareMode はキーを追加する前に設定する必要があります。
比べ方を変えても、元のセルの値は変わりません。判定のときだけキーを整えている、という点を押さえておくと、削除や修正に進むときに迷いません。
複数の列を組み合わせてキーにする
「氏名と電話番号が両方同じ」「伝票番号と明細番号が同じ」のように、複数の列で判定したい場合は、2つ目の列も配列に読み込み、間に vbTab を挟んでつなげたものをキーにします。区切りを入れておかないと、AB + C と A + BC が同じキーになってしまいます。
' 読み込み部分:2つ目のキー列(例:D列)も配列に取る
keys2 = ws.Range(ws.Cells(firstRow, 4), ws.Cells(lastRow, 4)).Value
' キーを作る2か所を、次のように置き換える
k = Trim$(CStr(keys(i, 1))) & vbTab & Trim$(CStr(keys2(i, 1)))
組み合わせる場合は、次の箇所もあわせて直してください。
keys2をVariantで宣言し、データが1行だけのときの読み込みもkeysと同じように分けるIsErrorの判定を2列分にする- 空白の判定を
Len(Replace(k, vbTab, "")) = 0にする - 一覧に書き出すときは、
Replace(key, vbTab, " / ")のように見やすい区切りに置き換える
結果の確かめ方
出力された結果が正しいかは、次の手順で確かめます。
- 件数の整合を見る:「重複一覧」シートの件数列の合計と、判定列で「重複」と付いた行の数が一致するかを確認します。たとえば
=SUM(重複一覧!B:B)と=COUNTIF(顧客一覧!H:H,"重複*")を並べて比べます。 - 実データを目で見る:一覧の行番号を見て元の行を開き、本当に同じ取引や同じ相手なのかを確認します。同じコードが別の相手に振られている、入力ミスで同じコードになった、といったケースは、削除ではなく修正の対象です。
- 並べ替えのタイミングに注意する:重複した行を並べて見たいときは、判定列でフィルターをかけるのが安全です。表を並べ替えると行の位置が変わり、一覧の行番号と合わなくなります。並べ替えた場合は、マクロを実行し直してください。
- データを直したら再実行する:判定列と一覧は実行した時点の結果です。データを修正したら、もう一度実行して最新の状態にします。
削除に進む前に決めておくこと
重複の一覧ができたら、すぐに消すのではなく、次の点を決めてから削除の作業に進みます。
- 重複したキーのうち、どの行を残すか(最初の行、日付が最新の行、金額などの項目が埋まっている行など)
- 残す行に、消す行の情報をまとめる必要がないか
- 削除前のブックをコピーとして保存したか
判定列と「重複一覧」を残しておけば、削除後に「何件消したか」「どのキーだったか」を後から説明できます。
