ねらい:マスタ同期を「差分抽出+安全反映」の型にして、常に最新状態を保つ
マスタ同期は「旧データ」と「新データ」を比べて、追加・削除・変更を正しく見極め、旧マスタを最新に更新する作業です。Excel関数や手作業だと抜け・誤更新が起きがちですが、VBAなら“配列I/O+Dictionary+正規化+差分ハッシュ”で一瞬・正確に処理できます。ここでは初心者でも貼って動くテンプレを、丸ごと置換(最も安全)と差分反映(柔軟)まで段階的に解説します。
共通基盤:配列I/O・キー正規化・比較列指定
一括読み書き・キー生成・列指定
' ModSync_Base.bas
Option Explicit
Private Const SEP As String = Chr$(30) ' 複合キーの安全な区切り
Public Function ReadRegion(ByVal ws As Worksheet, Optional ByVal topLeft As String = "A1") As Variant
ReadRegion = ws.Range(topLeft).CurrentRegion.Value
End Function
Public Sub WriteBlock(ByVal ws As Worksheet, ByVal a As Variant, ByVal topLeft As String)
ws.Range(topLeft).Resize(UBound(a, 1), UBound(a, 2)).Value = a
End Sub
Public Function NormKey(ByVal v As Variant) As String
NormKey = LCase$(Trim$(CStr(v))) ' 大小無視・前後空白除去
End Function
Public Function MakeKey2(ByVal v1 As Variant, ByVal v2 As Variant) As String
MakeKey2 = NormKey(v1) & SEP & NormKey(v2)
End Function
Public Function ColsToIndex(ByVal csv As String) As Long()
Dim p() As String: p = Split(csv, ",")
Dim idx() As Long: ReDim idx(0 To UBound(p))
Dim i As Long
For i = 0 To UBound(p): idx(i) = Range(Trim$(p(i)) & "1").Column: Next
ColsToIndex = idx
End Function
Public Function RowHash(ByVal a As Variant, ByVal r As Long, ByVal idx() As Long) As String
Dim i As Long, s As String
For i = LBound(idx) To UBound(idx)
s = s & NormKey(a(r, idx(i))) & "|" ' 正規化+区切りで比較の粒度を固定
Next
RowHash = s ' 実務はSHA-256へ置換可能
End Function
VB重要ポイントの深掘り
- 正規化は両側で必須。片側のみ適用はヒット漏れや誤差分の原因になります。
- 比較対象列は文字で指定(例:”B,D,F”)。列追加・入替にも壊れません。
- ハッシュは「比較に使う列のみ」で作る。ノイズ(更新日時など)を含めないのがコツです。
差分抽出:追加・削除・変更の一括検出
単一キーで差分抽出(貼って動く)
' ModSync_Diff.bas
Option Explicit
' oldSheet: 旧マスタ(A=キー)
' newSheet: 新マスタ(A=キー)
' compareColsCsv: 比較対象列(例 "B,C,D")
' outStart: 出力開始セル(例 "Z1")
Public Sub ExtractDiff(ByVal oldSheet As String, ByVal newSheet As String, ByVal compareColsCsv As String, ByVal outStart As String)
Dim o As Variant: o = ReadRegion(Worksheets(oldSheet))
Dim n As Variant: n = ReadRegion(Worksheets(newSheet))
Dim cmpIdx() As Long: cmpIdx = ColsToIndex(compareColsCsv)
Dim dOld As Object: Set dOld = CreateObject("Scripting.Dictionary"): dOld.CompareMode = 1
Dim hOld As Object: Set hOld = CreateObject("Scripting.Dictionary"): hOld.CompareMode = 1
Dim r As Long
For r = 2 To UBound(o, 1)
Dim k As String: k = NormKey(o(r, 1))
dOld(k) = r
hOld(k) = RowHash(o, r, cmpIdx)
Next
Dim out() As Variant: ReDim out(1 To 1, 1 To 4)
out(1, 1) = "Type": out(1, 2) = "Key": out(1, 3) = "OldHash": out(1, 4) = "NewHash"
Dim rowsOut As Long: rowsOut = 1
Dim seen As Object: Set seen = CreateObject("Scripting.Dictionary"): seen.CompareMode = 1
For r = 2 To UBound(n, 1)
Dim k As String: k = NormKey(n(r, 1))
Dim hn As String: hn = RowHash(n, r, cmpIdx)
seen(k) = True
If Not dOld.Exists(k) Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 4)
out(rowsOut, 1) = "ADDED": out(rowsOut, 2) = k: out(rowsOut, 4) = hn
ElseIf hOld(k) <> hn Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 4)
out(rowsOut, 1) = "CHANGED": out(rowsOut, 2) = k: out(rowsOut, 3) = hOld(k): out(rowsOut, 4) = hn
End If
Next
Dim k As Variant
For Each k In dOld.Keys
If Not seen.Exists(k) Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 4)
out(rowsOut, 1) = "DELETED": out(rowsOut, 2) = k: out(rowsOut, 3) = hOld(k)
End If
Next
WriteBlock Worksheets(oldSheet), out, outStart
End Sub
VB重要ポイントの深掘り
- ADDED/DELETED/CHANGEDの3種が一度に抽出でき、レビュー負荷が激減します。
- ハッシュ比較は「実質的な変更」だけを拾うための鍵。比較列の選定が品質を決めます。
同期のやり方①:丸ごと置換(最も安全でシンプル)
完全置換テンプレ
' ModSync_Replace.bas
Option Explicit
' 旧マスタを新マスタで丸ごと置換する(推奨:まずはこの方式で運用安定化)
Public Sub SyncByReplace(ByVal oldSheet As String, ByVal newSheet As String)
Dim n As Variant: n = ReadRegion(Worksheets(newSheet))
Worksheets(oldSheet).Cells.Clear
Worksheets(oldSheet).Range("A1").Resize(UBound(n, 1), UBound(n, 2)).Value = n
End Sub
VB重要ポイントの深掘り
- 誤更新を根絶しやすい。差分確認後に一発置換で「常に最新」にできます。
- 履歴や監査が必要なら、置換前に旧マスタをアーカイブ(別シート・CSV)保存しておくと安全です。
同期のやり方②:差分だけ反映(追加・削除・変更)
差分反映テンプレ(柔軟だが設計が要る)
' ModSync_ApplyDiff.bas
Option Explicit
' 比較対象列のみ更新し、追加・削除も反映。順序維持が不要な前提。
Public Sub SyncByApplyDiff(ByVal oldSheet As String, ByVal newSheet As String, ByVal compareColsCsv As String, Optional ByVal deleteModeClear As Boolean = True)
Dim o As Variant: o = ReadRegion(Worksheets(oldSheet))
Dim n As Variant: n = ReadRegion(Worksheets(newSheet))
Dim cmpIdx() As Long: cmpIdx = ColsToIndex(compareColsCsv)
' 新マスタ辞書(key → 行番号)
Dim dNew As Object: Set dNew = CreateObject("Scripting.Dictionary"): dNew.CompareMode = 1
Dim r As Long: For r = 2 To UBound(n, 1): dNew(NormKey(n(r, 1))) = r: Next
' 旧→変更/削除反映
For r = 2 To UBound(o, 1)
Dim k As String: k = NormKey(o(r, 1))
If dNew.Exists(k) Then
Dim rr As Long: rr = dNew(k)
Dim c As Long
For c = LBound(cmpIdx) To UBound(cmpIdx)
If CStr(o(r, cmpIdx(c))) <> CStr(n(rr, cmpIdx(c))) Then
o(r, cmpIdx(c)) = n(rr, cmpIdx(c)) ' 変更反映
End If
Next
Else
' 削除反映:空白化 or 実削除
If deleteModeClear Then
Dim c As Long: For c = 1 To UBound(o, 2): o(r, c) = "": Next
Else
' 実削除は行の再構築が必要:簡潔さ重視なら空白化を推奨
End If
End If
Next
' 追加反映:旧に無いキーを末尾追加
Dim rowsOut As Long: rowsOut = UBound(o, 1)
Dim k As Variant
For Each k In dNew.Keys
Dim found As Boolean: found = False
For r = 2 To UBound(o, 1)
If NormKey(o(r, 1)) = k Then found = True: Exit For
Next
If Not found Then
rowsOut = rowsOut + 1: ReDim Preserve o(1 To rowsOut, 1 To UBound(n, 2))
Dim rr As Long: rr = dNew(k)
Dim c As Long: For c = 1 To UBound(n, 2): o(rowsOut, c) = n(rr, c): Next
End If
Next
WriteBlock Worksheets(oldSheet), o, "A1"
End Sub
VB重要ポイントの深掘り
- 削除の扱いは運用で決める(空白化=監査しやすい/実削除=データ軽量)。まずは空白化が安全。
- 追加は末尾に入るため、順序重視なら最後に安定ソートを入れると良いです。
- 比較列に「更新日時」などを入れると毎回CHANGEDになるので、業務に効く列だけ選定します。
変更詳細の可視化:どの列が変わったかを示す
列別の変更一覧テンプレ
' ModSync_ChangedDetail.bas
Option Explicit
Public Sub ListChangedColumns(ByVal oldSheet As String, ByVal newSheet As String, ByVal compareColsCsv As String, ByVal outStart As String)
Dim o As Variant: o = ReadRegion(Worksheets(oldSheet))
Dim n As Variant: n = ReadRegion(Worksheets(newSheet))
Dim idx() As Long: idx = ColsToIndex(compareColsCsv)
Dim dOldRow As Object: Set dOldRow = CreateObject("Scripting.Dictionary"): dOldRow.CompareMode = 1
Dim r As Long: For r = 2 To UBound(o, 1): dOldRow(NormKey(o(r, 1))) = r: Next
Dim out() As Variant: ReDim out(1 To 1, 1 To 5)
out(1, 1) = "Key": out(1, 2) = "Column": out(1, 3) = "Old": out(1, 4) = "New": out(1, 5) = "Changed"
Dim rowsOut As Long: rowsOut = 1
Dim c As Long
For r = 2 To UBound(n, 1)
Dim k As String: k = NormKey(n(r, 1))
If dOldRow.Exists(k) Then
Dim ro As Long: ro = dOldRow(k)
For c = LBound(idx) To UBound(idx)
Dim col As Long: col = idx(c)
Dim oldVal As String: oldVal = CStr(o(ro, col))
Dim newVal As String: newVal = CStr(n(r, col))
If oldVal <> newVal Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 5)
out(rowsOut, 1) = k
out(rowsOut, 2) = Worksheets(oldSheet).Cells(1, col).Value
out(rowsOut, 3) = oldVal
out(rowsOut, 4) = newVal
out(rowsOut, 5) = "Y"
End If
Next
End If
Next
WriteBlock Worksheets(oldSheet), out, outStart
End Sub
VB重要ポイントの深掘り
- 列名付きでOld→Newを見せると承認・修正が速い。色分け(黄)で“変化”を強調するとさらに楽です。
- 値比較の正規化方針(大小無視・空白除去)を事前に合意しておくと、誤判定が減ります。
例題の通し方:商品マスタの差分→確認→同期
実行例
' ModSync_Example.bas
Option Explicit
Public Sub Demo_MasterSync()
' 1) 差分抽出(比較対象はB,C)
ExtractDiff "Master_Old", "Master_New", "B,C", "Z1"
' 2) 変更詳細(列別差分)
ListChangedColumns "Master_Old", "Master_New", "B,C", "AA1"
' 3a) 最も安全:丸ごと置換
SyncByReplace "Master_Old", "Master_New"
' 3b) 柔軟:差分だけ反映(選択)
' SyncByApplyDiff "Master_Old", "Master_New", "B,C", True
MsgBox "商品マスタの同期が完了しました。", vbInformation
End Sub
VB期待動作の確認ポイント
- ADDED/DELETED/CHANGEDが正しく分類され、列別の変更詳細も出る。
- 同期後、旧マスタは最新状態。必要なら差分だけ反映へ切替可能。
運用の深掘り:安全装置・見える化・拡張
運用の安全装置
- 同期前に旧マスタをCSVへ自動バックアップ。万一の巻き戻しが即可能。
- 差分件数(Added/Deleted/Changed)をメッセージとログに出すと監査が楽になります。
見える化
- 差分一覧に条件付き書式(ADDED=緑/DELETED=赤/CHANGED=黄)。レビューが一目で終わる。
- 種別別シートへ分割すると承認/配布がスムーズ。
拡張ポイント
- 複合キー(MakeKey2)や多列返却に対応。
- ハッシュをSHA-256に差し替え(より堅牢)。
- 順序維持が必要なら、同期後に安定ソート(キー列で)を標準化。
落とし穴と対策(深掘り)
正規化不足で誤差分
Trim/LCaseを両側に適用。必要なら全半角統一や記号除去の前処理も追加。
比較列にノイズが含まれる
更新日時・備考などは比較対象から外す。業務に効く列のみで差分を作る。
削除の扱いが曖昧
空白化か実削除かを最初に決める。監査がある現場は空白化が無難。
セル逐次書きで遅い
結果は配列で作成し、一括書き戻し。10万行でも高速でUIが固まりません。
まとめ:マスタ同期は「差分抽出+(置換 or 差分反映)」を型にして安定運用へ
- 差分抽出で“何が変わったか”を明示、最初は丸ごと置換で安全に最新化。
- 仕様が固まったら差分反映で柔軟に更新。削除方針・比較列設計・正規化を先に決める。
- 配列I/O+Dictionary+正規化+一括書き戻しの型を守れば、規模が大きくても壊れません。
