ねらい:2つのCSVの「追加・削除・変更」を一瞬で洗い出す
CSV差分ツールのゴールは、「前回のCSV」と「今回のCSV」を比べて、どの行が追加され、どの行が削除され、どの行の中身が変わったかを機械的に出すことです。
手作業で VLOOKUP やフィルタを駆使していると、時間もかかるし、ミスも出ます。
ここでは、Excel VBA で「キー列を基準に差分を取る」汎用テンプレを作り、どんなCSVでもほぼコピペで使える形にしていきます。
基本設計:キー列を決めて「旧CSV vs 新CSV」をDictionaryで突き合わせる
差分の考え方を先に言葉で決める
まず、「何をもって同じ行とみなすか」を決めます。
典型的には、顧客コード、商品コード、ID などの「一意なキー列」を1つ選びます。
このキーが同じ行同士を「同じレコード」とみなし、そこから差分を判定します。
旧CSVにあって新CSVにないキーは「削除された行」。
新CSVにあって旧CSVにないキーは「追加された行」。
両方にあるが、内容が違う行は「更新された行」。
この3種類を出せれば、実務上の差分チェックはほぼカバーできます。
VBA側では、この「キー → 行データ」の対応を Dictionary に載せて、高速に突き合わせます。
コア部品:CSVをシートに読み込み、配列+Dictionaryで差分を取る
前提となるシート構成と流れ
分かりやすくするために、次のような構成を前提にします。
旧CSVを読み込むシート:Old
新CSVを読み込むシート:New
差分結果を出すシート:Diff
CSV自体は、Excelの「データ取り込み」や手動で開いてコピーでも構いません。
テンプレとしては、「Old と New に同じ列構成でデータが並んでいる」状態からスタートします。
差分エンジン:キー列を基準に追加・削除・変更を判定するVBA
差分判定のメインプロシージャ
まずは「Old と New の差分を Diff に出す」メインの Sub です。
' ModCsvDiff_Core.bas
Option Explicit
Public Sub RunCsvDiff()
Dim wsOld As Worksheet, wsNew As Worksheet, wsDiff As Worksheet
Set wsOld = ThisWorkbook.Worksheets("Old")
Set wsNew = ThisWorkbook.Worksheets("New")
On Error Resume Next
Set wsDiff = ThisWorkbook.Worksheets("Diff")
On Error GoTo 0
If wsDiff Is Nothing Then
Set wsDiff = ThisWorkbook.Worksheets.Add
wsDiff.Name = "Diff"
Else
wsDiff.Cells.Clear
End If
Dim keyCol As Long
keyCol = 1 ' キー列(ここでは1列目=A列をキーとする)
Call CsvDiff_Compare(wsOld, wsNew, wsDiff, keyCol, 1)
MsgBox "CSV差分の抽出が完了しました。", vbInformation
End Sub
VBここでは、キー列を 1(A列)と決め打ちしていますが、実務では「ID列の番号」を引数で変えるだけです。
実際の差分ロジックは CsvDiff_Compare に切り出しておき、どのシートにも使い回せるようにします。
Dictionaryを使った差分ロジック本体
Public Sub CsvDiff_Compare(ByVal wsOld As Worksheet, _
ByVal wsNew As Worksheet, _
ByVal wsDiff As Worksheet, _
ByVal keyCol As Long, _
ByVal headerRow As Long)
Dim lastRowOld As Long, lastRowNew As Long
Dim lastCol As Long
lastRowOld = wsOld.Cells(wsOld.Rows.Count, keyCol).End(xlUp).Row
lastRowNew = wsNew.Cells(wsNew.Rows.Count, keyCol).End(xlUp).Row
lastCol = wsOld.Cells(headerRow, wsOld.Columns.Count).End(xlToLeft).Column
If lastRowOld <= headerRow And lastRowNew <= headerRow Then
MsgBox "データ行がありません。", vbInformation
Exit Sub
End If
Dim arrOld As Variant, arrNew As Variant
If lastRowOld > headerRow Then
arrOld = wsOld.Range(wsOld.Cells(headerRow, 1), wsOld.Cells(lastRowOld, lastCol)).Value
Else
arrOld = wsOld.Range(wsOld.Cells(headerRow, 1), wsOld.Cells(headerRow, lastCol)).Value
End If
If lastRowNew > headerRow Then
arrNew = wsNew.Range(wsNew.Cells(headerRow, 1), wsNew.Cells(lastRowNew, lastCol)).Value
Else
arrNew = wsNew.Range(wsNew.Cells(headerRow, 1), wsNew.Cells(headerRow, lastCol)).Value
End If
Dim dictOld As Object, dictNew As Object
Set dictOld = CreateObject("Scripting.Dictionary")
Set dictNew = CreateObject("Scripting.Dictionary")
dictOld.CompareMode = 1
dictNew.CompareMode = 1
Dim r As Long, c As Long
Dim key As String
For r = headerRow + 1 To lastRowOld
key = CStr(wsOld.Cells(r, keyCol).Value)
If key <> "" Then
If Not dictOld.Exists(key) Then
dictOld.Add key, r
End If
End If
Next
For r = headerRow + 1 To lastRowNew
key = CStr(wsNew.Cells(r, keyCol).Value)
If key <> "" Then
If Not dictNew.Exists(key) Then
dictNew.Add key, r
End If
End If
Next
Dim outRow As Long
outRow = 1
wsOld.Rows(headerRow).Copy wsDiff.Rows(outRow)
wsDiff.Cells(outRow, lastCol + 1).Value = "差分種別"
outRow = outRow + 1
Dim k As Variant
For Each k In dictOld.Keys
If Not dictNew.Exists(k) Then
wsOld.Rows(dictOld(k)).Copy wsDiff.Rows(outRow)
wsDiff.Cells(outRow, lastCol + 1).Value = "削除"
outRow = outRow + 1
End If
Next
For Each k In dictNew.Keys
If Not dictOld.Exists(k) Then
wsNew.Rows(dictNew(k)).Copy wsDiff.Rows(outRow)
wsDiff.Cells(outRow, lastCol + 1).Value = "追加"
outRow = outRow + 1
End If
Next
For Each k In dictOld.Keys
If dictNew.Exists(k) Then
Dim rowOld As Long, rowNew As Long
rowOld = dictOld(k)
rowNew = dictNew(k)
Dim isDiff As Boolean
isDiff = False
For c = 1 To lastCol
If wsOld.Cells(rowOld, c).Value <> wsNew.Cells(rowNew, c).Value Then
isDiff = True
Exit For
End If
Next
If isDiff Then
wsNew.Rows(rowNew).Copy wsDiff.Rows(outRow)
wsDiff.Cells(outRow, lastCol + 1).Value = "更新"
outRow = outRow + 1
End If
End If
Next
wsDiff.Columns.AutoFit
End Sub
VBここが CSV差分ツールの心臓部です。
ポイントをかみ砕いて整理します。
Old と New のデータを、それぞれ配列に読み込んでいますが、実際の比較はシートのセルを直接見ています。
Dictionary に「キー → 行番号」を登録し、Old側とNew側でキーの有無を見て「追加」「削除」を判定しています。
両方に存在するキーについては、全列をループして1セルでも違えば「更新」と判定し、新側の行を Diff に出力しています。
Diff シートの最後の列に「差分種別」(追加・削除・更新)を書いているので、あとからフィルタで絞り込めます。
例題:顧客マスタCSVの差分を取る具体的なイメージ
想定するCSVの中身
例えば、顧客マスタの CSV が次のような構造だとします。
1列目:CustomerID(顧客ID、これをキーにする)
2列目:CustomerName
3列目:Address
4列目:Tel
前回のCSVを Old シートに、今回のCSVを New シートに読み込んだ状態で、RunCsvDiff を実行します。
すると Diff シートに、旧・新どちらかの行がコピーされ、最後の列に「追加」「削除」「更新」が付いた一覧ができます。
例えば、Old にはあるが New にはない顧客ID「C001」は「削除」。
New にだけある顧客ID「C010」は「追加」。
両方にある「C005」で、住所だけ変わっていれば「更新」。
このように、「どの顧客にどんな変化があったか」が一目で分かるレポートになります。
重要ポイントの深掘り:CSV差分ツールを“実務で使える”形にするコツ
キー列の選び方がすべてを決める
差分ツールの精度は、「キー列の選び方」にほぼ依存します。
顧客IDや商品コードのような「一意なID」があるなら、それを迷わずキーにします。
もしIDがなく、名前やメールアドレスをキーにする場合は、重複や表記ゆれの影響を受けやすくなるので注意が必要です。
IDがない場合は、名寄せや正規化のテンプレと組み合わせて「比較用キー」を作ってから差分を取る、という二段構えも現実的です。
「更新」の判定はどこまで見るかを決める
上のテンプレでは、「全列のどこか1セルでも違えば更新」としています。
実務では、「この列は差分対象外」「更新判定はこの3列だけでいい」といった要件もよくあります。
その場合は、更新判定のループ部分を少し変えます。
全列ではなく、「差分対象列の配列」を用意して、その列だけを比較する。
例えば、cols = Array(2, 3, 4) のようにして、For Each で回す。
こうしておくと、「タイムスタンプ列は無視」「備考列は無視」といった柔軟な差分ルールを組み込めます。
行数が多いときのパフォーマンスと実務的な限界
CSV は数万行になることも珍しくありません。
今回のテンプレは Dictionary を使っているので、キーの突き合わせ自体はかなり高速です。
ただし、行数が増えると「行コピー」や「セル比較」の部分がボトルネックになってきます。
体感速度を上げるために、実務では次のような工夫をよくします。
処理前に Application.ScreenUpdating = False、処理後に True に戻す。
計算モードを手動にしておき、処理後に再計算する。
Diff に出す列を最小限に絞る(全部コピーせず、キーと重要列だけにする)。
これだけでも、数万行クラスの差分でも「待てる時間」に収まることが多いです。
CSVの読み込み自体もVBAで自動化できる
ここでは説明をシンプルにするために、「Old と New にCSVが読み込まれている前提」にしました。
実務では、ファイル選択ダイアログから CSV を選んで、自動で Old / New シートに読み込むところまで含めてツール化することが多いです。
Application.GetOpenFilename でファイルパスを取得し、Workbooks.Open でCSVを開いて、必要なシートにコピーして閉じる、
という流れを1つの Sub にまとめれば、「ボタンを押して2つのCSVを選ぶだけで差分レポートが出る」形にできます。
まとめ:CSV差分も「キー設計」と「差分種別の型」を決めれば、どのCSVにも流用できる
CSV差分ツールの本質は、次の二つです。
どの列をキーにして「同じ行」とみなすかを決める。
キーの有無と行内容の違いから、「追加」「削除」「更新」の3種に分類する。
Dictionary を使った CsvDiff_Compare を一度テンプレとして作っておけば、
あとはシート名とキー列、ヘッダ行を変えるだけで、顧客マスタでも商品マスタでもログCSVでも、そのまま使い回せます。
