変更箇所だけ転記
「前月Aと今月Bを比べて、変更されたところ“だけ”をAへ反映したい」「差分セルだけを別シートへ転記したい」——初心者でも壊れないテンプレを用意しました。ポイントは「キー突合」「数値/日付の型統一」「セル単位の転記」「監査しやすいログ出力」です。
共通ユーティリティ(速度・安全)
Option Explicit
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
Private Function NormKey(ByVal v As Variant) As String
NormKey = UCase$(Trim$(CStr(v)))
End Function
VB- 速度: 画面/イベント/計算停止でムダを削って安定。
- キー正規化: Trim+UCaseで表記揺れを吸収。
基本テンプレ:Bの変更セルだけAへ上書き(コードキー)
「A: コード/名称/単価」「B: コード/名称/単価」を突合し、違うセルだけAへ反映します。
Sub PatchDiff_Cells_ByKey()
SpeedOn
Dim wsA As Worksheet: Set wsA = Worksheets("A")
Dim wsB As Worksheet: Set wsB = Worksheets("B")
Dim rgA As Range: Set rgA = wsA.Range("A1").CurrentRegion
Dim rgB As Range: Set rgB = wsB.Range("A1").CurrentRegion
Dim vA As Variant: vA = rgA.Value
Dim vB As Variant: vB = rgB.Value
'列想定:1=コード, 2=名称, 3=単価(見出しあり)
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim i As Long, k As String
For i = 2 To UBound(vB, 1)
k = NormKey(vB(i, 1))
If Len(k) > 0 Then dictB(k) = i
Next
'変更ログ(任意):別シートに残す
Dim wsLog As Worksheet
On Error Resume Next: Set wsLog = Worksheets("変更転記ログ"): On Error GoTo 0
If wsLog Is Nothing Then Set wsLog = Worksheets.Add: wsLog.Name = "変更転記ログ"
wsLog.Cells.Clear
wsLog.Range("A1:D1").Value = Array("コード", "項目", "旧(A)", "新(B)")
Dim rLog As Long: rLog = 2
'Aへセル単位で反映
Dim r As Long, rowA As Long, rowB As Long
For r = 2 To UBound(vA, 1)
k = NormKey(vA(r, 1))
If Len(k) = 0 Or Not dictB.Exists(k) Then GoTo contA
rowA = rgA.Row + r - 1
rowB = rgB.Row + dictB(k) - 1
'名称(文字)
If CStr(vA(r, 2)) <> CStr(vB(dictB(k), 2)) Then
wsA.Cells(rowA, rgA.Column + 1).Value = vB(dictB(k), 2)
wsLog.Cells(rLog, 1).Value = vA(r, 1)
wsLog.Cells(rLog, 2).Value = "名称"
wsLog.Cells(rLog, 3).Value = vA(r, 2)
wsLog.Cells(rLog, 4).Value = vB(dictB(k), 2)
rLog = rLog + 1
End If
'単価(数値)
Dim aPrice As Double: aPrice = CDbl(Val(vA(r, 3)))
Dim bPrice As Double: bPrice = CDbl(Val(vB(dictB(k), 3)))
If aPrice <> bPrice Then
wsA.Cells(rowA, rgA.Column + 2).Value = bPrice
wsLog.Cells(rLog, 1).Value = vA(r, 1)
wsLog.Cells(rLog, 2).Value = "単価"
wsLog.Cells(rLog, 3).Value = aPrice
wsLog.Cells(rLog, 4).Value = bPrice
rLog = rLog + 1
End If
contA:
Next
wsLog.Rows(1).Font.Bold = True
wsLog.Columns.AutoFit
SpeedOff
End Sub
VB- ポイント
- 変更セルだけを上書きするので安全(未一致キーは触らない)。
- 数値は Val→CDblで比較・転記、書式は後で整える。
見出し名で柔軟対応(項目増やしても壊れない)
比較・転記対象を見出し名で指定。増減に強く、数値項目は数値比較で上書き。
Sub PatchDiff_Cells_ByHeaders()
SpeedOn
Dim wsA As Worksheet: Set wsA = Worksheets("A")
Dim wsB As Worksheet: Set wsB = Worksheets("B")
Dim rgA As Range: Set rgA = wsA.Range("A1").CurrentRegion
Dim rgB As Range: Set rgB = wsB.Range("A1").CurrentRegion
Dim vA As Variant: vA = rgA.Value
Dim vB As Variant: vB = rgB.Value
'キー見出し
Dim cKeyA As Long: cKeyA = FindCol(rgA, "コード")
Dim cKeyB As Long: cKeyB = FindCol(rgB, "コード")
If cKeyA = 0 Or cKeyB = 0 Then SpeedOff: MsgBox "キー見出し不足": Exit Sub
'比較・転記項目(見出し名配列)
Dim fields As Variant: fields = Array("名称", "カテゴリ", "単価", "在庫")
Dim i As Long
Dim mapA() As Long: ReDim mapA(LBound(fields) To UBound(fields))
Dim mapB() As Long: ReDim mapB(LBound(fields) To UBound(fields))
For i = LBound(fields) To UBound(fields)
mapA(i) = FindCol(rgA, fields(i))
mapB(i) = FindCol(rgB, fields(i))
If mapA(i) = 0 Or mapB(i) = 0 Then SpeedOff: MsgBox "見出し不足:" & fields(i): Exit Sub
Next
'B辞書(キー→行番号)
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim r As Long, k As String
For r = 2 To UBound(vB, 1)
k = NormKey(vB(r, cKeyB))
If Len(k) > 0 Then dictB(k) = r
Next
'ログ
Dim wsLog As Worksheet: Set wsLog = EnsureSheet("変更転記ログ", True)
wsLog.Range("A1:D1").Value = Array("コード", "項目", "旧(A)", "新(B)")
Dim rLog As Long: rLog = 2
'Aへセル単位で反映
For r = 2 To UBound(vA, 1)
k = NormKey(vA(r, cKeyA))
If Len(k) = 0 Or Not dictB.Exists(k) Then GoTo contA
Dim rb As Long: rb = dictB(k)
Dim rowA As Long: rowA = rgA.Row + r - 1
For i = LBound(fields) To UBound(fields)
Dim va As Variant: va = vA(r, mapA(i))
Dim vb As Variant: vb = vB(rb, mapB(i))
Dim isNum As Boolean: isNum = (fields(i) Like "*単価*" Or fields(i) Like "*金額*" Or fields(i) Like "*在庫*")
Dim diff As Boolean
If isNum Then
diff = (CDbl(Val(va)) <> CDbl(Val(vb)))
ElseIf IsDate(va) Or IsDate(vb) Then
diff = (Format$(CDate(va), "yyyy-mm-dd") <> Format$(CDate(vb), "yyyy-mm-dd"))
Else
diff = (CStr(va) <> CStr(vb))
End If
If diff Then
wsA.Cells(rowA, mapA(i)).Value = vb
wsLog.Cells(rLog, 1).Value = vA(r, cKeyA)
wsLog.Cells(rLog, 2).Value = fields(i)
wsLog.Cells(rLog, 3).Value = va
wsLog.Cells(rLog, 4).Value = vb
rLog = rLog + 1
End If
Next
contA:
Next
wsLog.Rows(1).Font.Bold = True
wsLog.Columns.AutoFit
SpeedOff
End Sub
Private Function FindCol(ByVal rg As Range, ByVal headerName As String) As Long
Dim hit As Range
Set hit = rg.Rows(1).Find(What:=headerName, LookAt:=xlWhole, LookIn:=xlValues, MatchCase:=False)
FindCol = IIf(hit Is Nothing, 0, hit.Column)
End Function
Private Function EnsureSheet(ByVal name As String, Optional ByVal clear As Boolean = True) As Worksheet
Dim ws As Worksheet
On Error Resume Next
Set ws = Worksheets(name)
On Error GoTo 0
If ws Is Nothing Then
Set ws = Worksheets.Add(After:=Worksheets(Worksheets.Count))
ws.Name = name
End If
If clear Then ws.Cells.Clear
Set EnsureSheet = ws
End Function
VB- ポイント
- 項目の増減に対応。数値/日付/文字で比較方法を自動切替。
- 見出し名から列を取得して壊れにくい。
変更セルだけを別シートへ転記(監査・配布用)
Aは触らず、差分セルを「コード・項目・旧・新・A行・B行」で一覧化します。
Sub ExportDiffCells_ToSheet()
SpeedOn
Dim rgA As Range: Set rgA = Worksheets("A").Range("A1").CurrentRegion
Dim rgB As Range: Set rgB = Worksheets("B").Range("A1").CurrentRegion
Dim vA As Variant: vA = rgA.Value
Dim vB As Variant: vB = rgB.Value
Dim cKeyA As Long: cKeyA = FindCol(rgA, "コード")
Dim cKeyB As Long: cKeyB = FindCol(rgB, "コード")
Dim fields As Variant: fields = Array("名称", "単価", "在庫") '必要に応じて追加
Dim i As Long
Dim mapA() As Long: ReDim mapA(LBound(fields) To UBound(fields))
Dim mapB() As Long: ReDim mapB(LBound(fields) To UBound(fields))
For i = LBound(fields) To UBound(fields)
mapA(i) = FindCol(rgA, fields(i))
mapB(i) = FindCol(rgB, fields(i))
If mapA(i) = 0 Or mapB(i) = 0 Then SpeedOff: MsgBox "見出し不足:" & fields(i): Exit Sub
Next
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim r As Long, k As String
For r = 2 To UBound(vB, 1)
k = NormKey(vB(r, cKeyB))
If Len(k) > 0 Then dictB(k) = r
Next
Dim wsOut As Worksheet: Set wsOut = EnsureSheet("変更セル一覧", True)
wsOut.Range("A1:F1").Value = Array("コード", "項目", "旧(A)", "新(B)", "A行", "B行")
Dim w As Long: w = 2
For r = 2 To UBound(vA, 1)
k = NormKey(vA(r, cKeyA))
If Len(k) = 0 Or Not dictB.Exists(k) Then GoTo contA
Dim rb As Long: rb = dictB(k)
For i = LBound(fields) To UBound(fields)
Dim va As Variant: va = vA(r, mapA(i))
Dim vb As Variant: vb = vB(rb, mapB(i))
Dim diff As Boolean
If fields(i) Like "*単価*" Or fields(i) Like "*在庫*" Or fields(i) Like "*金額*" Then
diff = (CDbl(Val(va)) <> CDbl(Val(vb)))
Else
diff = (CStr(va) <> CStr(vb))
End If
If diff Then
wsOut.Cells(w, 1).Value = vA(r, cKeyA)
wsOut.Cells(w, 2).Value = fields(i)
wsOut.Cells(w, 3).Value = va
wsOut.Cells(w, 4).Value = vb
wsOut.Cells(w, 5).Value = rgA.Row + r - 1
wsOut.Cells(w, 6).Value = rgB.Row + rb - 1
w = w + 1
End If
Next
contA:
Next
wsOut.Rows(1).Font.Bold = True
wsOut.Columns.AutoFit
SpeedOff
End Sub
VB- ポイント
- 影響範囲の確認やレビューに便利。後から“必要なセルだけ”反映も可。
複合キー(コード×年月)で変更箇所だけ転記
年月単位で上書きしたいときは複合キー文字連結で突合。
Private Function BuildKey2(ByVal code As Variant, ByVal ymd As Variant) As String
Dim ym As String
If IsDate(ymd) Then ym = Format$(CDate(ymd), "yyyy-mm") Else ym = CStr(ymd)
BuildKey2 = NormKey(code) & "|" & UCase$(Trim$(ym))
End Function
Sub PatchDiff_Cells_MultiKey()
SpeedOn
'列想定:A=コード, B=年月, C=名称, D=単価
Dim rgA As Range: Set rgA = Worksheets("A").Range("A1").CurrentRegion
Dim rgB As Range: Set rgB = Worksheets("B").Range("A1").CurrentRegion
Dim vA As Variant: vA = rgA.Value
Dim vB As Variant: vB = rgB.Value
'B辞書(複合キー→行)
Dim dictB As Object: Set dictB = CreateObject("Scripting.Dictionary")
Dim i As Long, key As String
For i = 2 To UBound(vB, 1)
key = BuildKey2(vB(i, 1), vB(i, 2))
dictB(key) = i
Next
'ログ
Dim wsLog As Worksheet: Set wsLog = EnsureSheet("変更転記ログ_複合", True)
wsLog.Range("A1:E1").Value = Array("コード|年月", "項目", "旧(A)", "新(B)", "A行")
Dim w As Long: w = 2
'反映
Dim r As Long
For r = 2 To UBound(vA, 1)
key = BuildKey2(vA(r, 1), vA(r, 2))
If Not dictB.Exists(key) Then GoTo contA
Dim rb As Long: rb = dictB(key)
Dim rowA As Long: rowA = rgA.Row + r - 1
'名称
If CStr(vA(r, 3)) <> CStr(vB(rb, 3)) Then
wsA.Cells(rowA, rgA.Column + 2).Value = vB(rb, 3)
wsLog.Cells(w, 1).Value = key
wsLog.Cells(w, 2).Value = "名称"
wsLog.Cells(w, 3).Value = vA(r, 3)
wsLog.Cells(w, 4).Value = vB(rb, 3)
wsLog.Cells(w, 5).Value = rowA
w = w + 1
End If
'単価
If CDbl(Val(vA(r, 4))) <> CDbl(Val(vB(rb, 4))) Then
wsA.Cells(rowA, rgA.Column + 3).Value = CDbl(Val(vB(rb, 4)))
wsLog.Cells(w, 1).Value = key
wsLog.Cells(w, 2).Value = "単価"
wsLog.Cells(w, 3).Value = CDbl(Val(vA(r, 4)))
wsLog.Cells(w, 4).Value = CDbl(Val(vB(rb, 4)))
wsLog.Cells(w, 5).Value = rowA
w = w + 1
End If
contA:
Next
wsLog.Rows(1).Font.Bold = True
wsLog.Columns.AutoFit
SpeedOff
End Sub
VB- ポイント
- 年月の揺れは yyyy-mm 統一。
- 区切りは「|」など安全な記号。
実務の落とし穴と対策
- キー表記揺れで誤突合
- 対策: NormKey(Trim+UCase)。必要なら半角化や不要記号除去も加える。
- 数値・日付の型ズレで誤判定
- 対策: 数値は Val→CDbl、日付は IsDate→Format(“yyyy-mm-dd”) で比較・転記。
- 列順変更で壊れる
- 対策: 見出し名から列を特定(FindCol)。ハードコード禁止。
- 転記の監査が難しい
- 対策: 変更ログを必ず残す(コード/項目/旧/新)。必要なら元の値を別列に退避。
例題で練習
'例1:コードキーの変更セルだけをAへ反映
Sub Example_PatchBasic()
PatchDiff_Cells_ByKey
End Sub
'例2:見出し名で項目を柔軟指定して変更セルだけ反映
Sub Example_PatchByHeaders()
PatchDiff_Cells_ByHeaders
End Sub
'例3:変更セルだけを一覧に転記(監査用)
Sub Example_ExportDiff()
ExportDiffCells_ToSheet
End Sub
'例4:複合キー(コード×年月)で変更セルだけ反映
Sub Example_PatchMultiKey()
PatchDiff_Cells_MultiKey
End Sub
VB