Excel VBA 逆引き集 | 実務テンプレ完全版(超再利用部品) – CSV差分ツール

Excel VBA
スポンサーリンク

ねらい: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でも、そのまま使い回せます。

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