ねらい:設定シートを書くだけで「差分チェック」が回る世界にする
「旧データと新データの差分を取りたい」
「マスタの更新内容を確認したい」
「システムAの出力とシステムBの出力が本当に同じか確かめたい」
こういう“差分チェック”を、そのたびに関数やマクロで組んでいませんか。
ノーコード差分ツールのゴールは、ここを一気に楽にすることです。
どのシートとどのシートを比べるか。
どの列をキーにするか。
どの列の差分を見たいか。
結果をどのシートに出したいか。
これを全部「設定シート」に書くだけで、VBAのコードは一切触らずに差分チェックが走る。
そのための“汎用差分エンジン”をテンプレとして作ります。
全体設計:差分設定シート+汎用差分エンジン
シート構成のイメージ
基本の構成は、次の三種類です。
旧データシート(例:Old)
新データシート(例:New)
差分結果シート(例:Diff)
そして、それらをどう比べるかを書く「差分設定シート(ConfigDiff)」を用意します。
VBA側は、「ConfigDiff を読み、そこに書かれたルール通りに Old と New を比べて Diff に出す」だけの汎用エンジンです。
ポイントは、「差分のロジック(どの列をキーに、どの列を比較するか)」をコードに書かないこと。
すべて ConfigDiff に書き出してしまうことです。
差分設定シートの設計:何をどう書くか
1行=1つの差分ルールという形にする
ConfigDiff シートに、次のような列を用意します。
A列:有効フラグ(Y の行だけ処理)
B列:LeftSheet(左側シート名。旧データなど)
C列:RightSheet(右側シート名。新データなど)
D列:KeyCols(キーにする列。カンマ区切り)
E列:CompareCols(比較する列。カンマ区切り)
F列:OutputSheet(差分結果シート名)
G列:Mode(差分モード。ALL/CHANGED/ONLY_LEFT/ONLY_RIGHT など)
例として、こんな感じをイメージしてください。
A: Y
B: Old
C: New
D: A,B
E: C,D,E
F: Diff
G: ALL
これは、「Old と New を A列+B列で突き合わせ、C〜E列の値の違いを Diff に出す。差分の種類は全部(変更・片側のみ)出す」という意味になります。
ここまで決めてしまえば、「どんな差分を取りたいか」はすべて表で表現できます。
VBAは、この表を読み取って動くだけです。
コア部品:行を「キー→行データ」にまとめる汎用関数
キーを文字列として組み立てる
複数列をキーにするために、「行の中からキー文字列を作る」関数を用意します。
' ModDiffEngine.bas
Option Explicit
Private Function BuildKeyFromRow(ByVal rowData As Variant, ByVal keyCols As Variant) As String
Dim i As Long
Dim parts() As String
ReDim parts(1 To UBound(keyCols) + 1)
For i = LBound(keyCols) To UBound(keyCols)
parts(i + 1) = CStr(rowData(1, CLng(keyCols(i))))
Next
BuildKeyFromRow = Join(parts, "||")
End Function
VBkeyCols は「{1,2}」のような列番号配列を想定しています。
キーは「キー1||キー2」のような文字列になります(区切り文字は被らないものにしておく)。
左右のシートを Dictionary に載せる
差分を取るために、「キー→行データ」を Dictionary に載せます。
Private Function LoadSheetToDict( _
ByVal sheetName As String, _
ByVal keyCols As Variant) As Object
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets(sheetName)
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
If lastRow < 2 Then
Set LoadSheetToDict = CreateObject("Scripting.Dictionary")
Exit Function
End If
Dim data As Variant
data = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)).Value
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = 1
Dim r As Long
Dim key As String
For r = 2 To lastRow
key = BuildKeyFromRow(Application.Index(data, r, 0), keyCols)
If key <> "" Then
If Not dict.Exists(key) Then
dict.Add key, Application.Index(data, r, 0)
End If
End If
Next
Set LoadSheetToDict = dict
End Function
VBここでの重要ポイントは、「行全体(1行分)を丸ごと配列として Dictionary に持たせている」ことです。
これで、キーからその行の全列にアクセスできるようになります。
差分ルールを読み込む:ConfigDiff → ルール配列
差分ルール1行分の型を定義する
Private Type DiffRule
Enabled As Boolean
LeftSheet As String
RightSheet As String
KeyCols As Variant
CompareCols As Variant
OutputSheet As String
Mode As String
End Type
VB列番号は数字で扱いたいので、列記号(A,B,C…)でも番号に変換できるようにします。
Private Function ColToNumber(ByVal colRef As Variant) As Long
If IsNumeric(colRef) Then
ColToNumber = CLng(colRef)
Else
ColToNumber = Range(CStr(colRef) & "1").Column
End If
End Function
VBカンマ区切りの列指定「A,B,C」を列番号配列に変換する関数も用意します。
Private Function ParseCols(ByVal s As String) As Variant
Dim parts As Variant
parts = Split(s, ",")
Dim cols() As Long
ReDim cols(0 To UBound(parts))
Dim i As Long
For i = 0 To UBound(parts)
cols(i) = ColToNumber(Trim$(parts(i)))
Next
ParseCols = cols
End Function
VBConfigDiff から DiffRule 配列を作る
Private Function LoadDiffRules() As Variant
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("ConfigDiff")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then
LoadDiffRules = Empty
Exit Function
End If
Dim data As Variant
data = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 7)).Value
Dim rules() As DiffRule
ReDim rules(1 To UBound(data, 1))
Dim i As Long
For i = 1 To UBound(data, 1)
rules(i).Enabled = (UCase$(CStr(data(i, 1))) = "Y")
rules(i).LeftSheet = CStr(data(i, 2))
rules(i).RightSheet = CStr(data(i, 3))
rules(i).KeyCols = ParseCols(CStr(data(i, 4)))
rules(i).CompareCols = ParseCols(CStr(data(i, 5)))
rules(i).OutputSheet = CStr(data(i, 6))
rules(i).Mode = UCase$(CStr(data(i, 7)))
Next
LoadDiffRules = rules
End Function
VBこれで、「ConfigDiff の内容」が DiffRule の配列としてメモリに載ります。
汎用差分エンジン:設定に従って差分を取る
速度アップ用のラッパー
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
VBメイン処理:ルールごとに差分をかける
Public Sub RunDiffTool()
Dim rules As Variant
rules = LoadDiffRules()
If IsEmpty(rules) Then
MsgBox "ConfigDiffに差分設定がありません。", vbInformation
Exit Sub
End If
SpeedOn
Dim i As Long
For i = LBound(rules) To UBound(rules)
If rules(i).Enabled Then
ApplyDiffRule rules(i)
End If
Next i
SpeedOff
MsgBox "ノーコード差分ツールの処理が完了しました。", vbInformation
End Sub
VB1つの差分ルールを適用する汎用処理
ここが心臓部です。
Private Sub ApplyDiffRule(ByRef rule As DiffRule)
Dim dictL As Object, dictR As Object
dictL = Nothing: dictR = Nothing
Set dictL = LoadSheetToDict(rule.LeftSheet, rule.KeyCols)
Set dictR = LoadSheetToDict(rule.RightSheet, rule.KeyCols)
Dim wsOut As Worksheet
On Error Resume Next
Set wsOut = ThisWorkbook.Worksheets(rule.OutputSheet)
On Error GoTo 0
If wsOut Is Nothing Then
Set wsOut = ThisWorkbook.Worksheets.Add
wsOut.Name = rule.OutputSheet
Else
wsOut.Cells.Clear
End If
Dim maxCols As Long
maxCols = Application.WorksheetFunction.Max( _
ThisWorkbook.Worksheets(rule.LeftSheet).Cells(1, Columns.Count).End(xlToLeft).Column, _
ThisWorkbook.Worksheets(rule.RightSheet).Cells(1, Columns.Count).End(xlToLeft).Column)
wsOut.Cells(1, 1).Value = "DiffType"
wsOut.Cells(1, 2).Value = "Key"
Dim outRow As Long
outRow = 2
Dim k As Variant
For Each k In dictL.Keys
Dim inRight As Boolean
inRight = dictR.Exists(k)
If Not inRight Then
If rule.Mode = "ALL" Or rule.Mode = "ONLY_LEFT" Then
wsOut.Cells(outRow, 1).Value = "ONLY_LEFT"
wsOut.Cells(outRow, 2).Value = CStr(k)
outRow = outRow + 1
End If
Else
If rule.Mode = "ALL" Or rule.Mode = "CHANGED" Then
If HasChanged(dictL(k), dictR(k), rule.CompareCols) Then
wsOut.Cells(outRow, 1).Value = "CHANGED"
wsOut.Cells(outRow, 2).Value = CStr(k)
outRow = outRow + 1
End If
End If
End If
Next
For Each k In dictR.Keys
If Not dictL.Exists(k) Then
If rule.Mode = "ALL" Or rule.Mode = "ONLY_RIGHT" Then
wsOut.Cells(outRow, 1).Value = "ONLY_RIGHT"
wsOut.Cells(outRow, 2).Value = CStr(k)
outRow = outRow + 1
End If
End If
Next
wsOut.Columns.AutoFit
End Sub
VBここでは、まず左側のキーを全部見て、「右にない」か「右にあるが中身が違う」かを判定しています。
次に、右側のキーを見て、「左にない」ものを拾っています。
差分の種類は、DiffType 列に
ONLY_LEFT(旧にだけある)
ONLY_RIGHT(新にだけある)
CHANGED(両方にあるが中身が違う)
として出しています。
値が変わっているかどうかを判定する関数
CompareCols に指定された列だけを見て、「違うかどうか」を判定します。
Private Function HasChanged( _
ByVal rowL As Variant, _
ByVal rowR As Variant, _
ByVal compareCols As Variant) As Boolean
Dim i As Long
For i = LBound(compareCols) To UBound(compareCols)
Dim c As Long
c = compareCols(i)
If CStr(rowL(1, c)) <> CStr(rowR(1, c)) Then
HasChanged = True
Exit Function
End If
Next
HasChanged = False
End Function
VBここでの重要ポイントは、「どの列を比較するかも設定で決められる」ことです。
ConfigDiff の CompareCols を変えるだけで、「金額だけ見る」「名称だけ見る」「全部見る」を切り替えられます。
例題:顧客マスタの旧版と新版の差分を取る
想定するシート構成
Old シート(旧顧客マスタ)
A列:顧客コード
B列:顧客名
C列:住所
D列:電話番号
New シート(新顧客マスタ)
同じ列構成で、新しいデータが入っているとします。
やりたいことは、
顧客コードをキーにして、
旧にしかない顧客、新にしかない顧客、
顧客名・住所・電話番号のどれかが変わった顧客を洗い出すことです。
ConfigDiff シートには、次のように書きます。
A列:Y
B列:Old
C列:New
D列:A
E列:B,C,D
F列:Diff
G列:ALL
これで、「Old と New を A列(顧客コード)で突き合わせ、B〜D列の違いを Diff に出す」という設定になります。
RunDiffTool を実行すると、Diff シートには
DiffType
Key
という2列が出て、行ごとに
ONLY_LEFT(旧にだけいる顧客)
ONLY_RIGHT(新にだけいる顧客)
CHANGED(両方にいるが、名前・住所・電話のどれかが変わった顧客)
が一覧で出ます。
ここからさらに、「どの項目がどう変わったか」まで出したければ、
HasChanged の中身を拡張して、「変更前」「変更後」を別列に出すようにすればOKです。
それも、CompareCols を使って柔軟に制御できます。
重要ポイントの深掘り:ノーコード差分ツールを“現場で回る”形にするコツ
ConfigDiff の「検証」を入れておくべき理由
完全ノーコードにすると、現場の人が自由に設定を変えられる反面、
シート名のタイプミス、列指定のミス、Mode の誤記なども起こりやすくなります。
実務で使うなら、RunDiffTool の最初に
指定された LeftSheet/RightSheet/OutputSheet が存在するか
KeyCols/CompareCols に指定された列が存在するか
Mode が許可された値(ALL/CHANGED/ONLY_LEFT/ONLY_RIGHT)か
をチェックし、問題があれば処理を止めてメッセージを出す、という“安全装置”を付けておくと安心です。
キーの正規化(全角半角・大文字小文字)をどこでやるか
差分の精度は、「キーがどれだけ揃っているか」に依存します。
顧客コードや商品コードのような“きれいなコード”ならそのままで良いですが、
メールアドレスや名称などをキーにする差分では、正規化が必要です。
その場合は、BuildKeyFromRow の中で
値を Trim する
全角半角を揃える
大文字小文字を揃える
などの処理を挟むか、別途 NormalizeKey 関数を作って通すようにします。
重要なのは、「左と右で同じ正規化をかける」ことです。
差分の粒度をどうするか(行単位か項目単位か)
今回のテンプレは、「行単位で差分があるかどうか」を判定しています。
もし「どの項目が変わったかまで知りたい」なら、HasChanged を拡張して
変わった列名
旧値
新値
を Diff シートに出すようにすれば、項目単位の差分一覧が作れます。
ここも、「CompareCols に何を指定するか」で柔軟に制御できます。
まずは行単位の CHANGED だけで運用を始め、必要になったら項目単位の出力を追加する、という段階的な導入がおすすめです。
まとめ:ノーコード差分ツールは「差分ロジックを表にして、コードはエンジンに閉じ込める」発想
このテンプレの本質は、次の三つです。
どのシート同士を、どの列をキーに、どの列を比較するかを、ConfigDiff という表にすべて書き出す。
VBA側は、その表を読み取り、配列と Dictionary を使って高速に差分を取る汎用エンジンだけを持つ。
差分内容を変えたいときは、ConfigDiff の行を追加・修正するだけで、コードは一切触らない。
これができると、「顧客マスタの差分」「商品マスタの差分」「CSVの旧版と新版の差分」など、
差分パターンがいくつ増えても、ツールは1本で済みます。
