Macro adjustment - compare two sheets

Robert Mika

MrExcel MVP
Joined
Jun 29, 2009
Messages
7,256
I have report downloading every day.
It has around 500 rows.
Every day I need to go through all of the lines to find small changes.

This macro is comparing two rows in sheets and will copy all differences to Sheet3.

Code:
Public Sub ReconReport()
    Dim rngCell As Range
    
    For Each rngCell In Worksheets("Sheet1").UsedRange
        If Not rngCell = Worksheets("Sheet2").Cells(rngCell.Row, rngCell.Column) Then _
            Let Worksheets("Sheet3").Cells(rngCell.Row, rngCell.Column) = rngCell
    Next
End Sub
The problem is that it is comparing rows not actually entries.
(Data in row of today's report (let say 2) can be in different row on yesterday’s (let say 5)
Is the a way to compare those entries and paste the difference to another sheet?
 
Hi Nil, I've added another paranmeter "Show Unchanged Cells" which, in your case needs to be "Yes"
Code:
Dim mbaKeyFields() As Boolean
Dim mbaKeyCols1() As Boolean
Dim mbaKeyCols2() As Boolean
Dim mbaHeadingsInfo() As Boolean
Dim mbIgnoreCase As Boolean
Dim mbDisplayOutputHeadings As Boolean
Dim mbFilterKey As Boolean
Dim mbShowUnchangedCells As Boolean

Dim miMaxColumns As Integer
Dim miaHeadingCols1() As Integer
Dim miaHeadingCols2() As Integer
Dim miaKeyFields1() As Integer
Dim miaKeyFields2() As Integer

Const mlResultsPtrDupKey1 As Long = 1
Const mlResultsPtrDupKey2 As Long = 2
Const mlResultsPtrMismatched As Long = 3
Const mlResultsPtrMatched As Long = 4
Const mlResultsPtrData1Only As Long = 5
Const mlResultsPtrData2Only As Long = 6

Dim mlReportRow As Long
Dim mlaResultsSheetsPtrs(1 To 6) As Long

Dim msIgnoreCharacters As String
Dim msOnlyCharacters As String
Dim msaCompareSheets() As String
Dim msResultsSheet As String
Dim msaHeadings1() As String
Dim msaHeadings2() As String
Dim msaHeadingRows() As String
Dim msaResultsSheets(1 To 6) As String

Dim mvaDuplicateKeys As Variant

Dim mwbOld As Workbook
Dim mwbNew As Workbook

Dim mwsReportSheet As Worksheet
Dim mwsaResultsSheets(1 To 6) As Worksheet

Sub CompareSheets()
Dim bChanged As Boolean, baChanged() As Boolean

Dim iColEnd As Integer, iCol As Integer, iCol1 As Integer, iCol2 As Integer

Dim lSheetPtr As Long
Dim lRow1 As Long
Dim lRow2 As Long
Dim lHeadingRow1 As Long
Dim lHeadingRow2 As Long

Dim objDictOld As Object, objDictNew As Object

Dim sCompareString1 As String
Dim sCompareString2 As String

Dim vKeys As Variant, vKey As Variant
Dim vaInput() As Variant, vaOutput() As Variant, vaOutput2() As Variant
Dim vaInputOld As Variant, vaInputNew As Variant
Dim vaHeadings() As Variant

Dim wsOld As Worksheet, wsNew As Worksheet

mlDuplicateKeysCount = 0
mlReportRow = 0

If GetParameters = False Then Exit Sub

For lSheetPtr = 1 To UBound(mwsaResultsSheets)
    Set mwsReportSheet = mwsaResultsSheets(lSheetPtr)
    If Not (mwsReportSheet Is Nothing) Then
        With mwsReportSheet.Cells
            .ClearFormats
            .ClearContents
        End With
        If mbDisplayOutputHeadings = True Then
            ReDim vaHeadings(1 To 1, 1 To UBound(msaHeadings1) + 2)
            For iCol = 0 To UBound(msaHeadings1)
                If msaHeadings1(iCol) = msaHeadings2(iCol) Then
                    vaHeadings(1, iCol + 2) = msaHeadings1(iCol)
                Else
                    vaHeadings(1, iCol + 2) = msaHeadings1(iCol) & " / " & msaHeadings2(iCol)
                End If
            Next iCol
            mwsReportSheet.Range("A1", mwsReportSheet.Cells(1, UBound(vaHeadings, 2)).Address).Value = vaHeadings
            mwsReportSheet.CustomProperties.Item(1).Value = 1
        Else
            mwsReportSheet.CustomProperties.Item(1).Value = 0
        End If
    End If
Next lSheetPtr

lHeadingRow1 = Val(msaHeadingRows(0))
If lHeadingRow1 < 1 Then lHeadingRow1 = 1

lHeadingRow2 = Val(msaHeadingRows(UBound(msaHeadingRows)))
If lHeadingRow2 < 1 Then lHeadingRow2 = 1

Set wsOld = GetWorksheet(WSName:=msaCompareSheets(0), WB:=mwbOld)
If wsOld Is Nothing Then Exit Sub

If PopulateHeadingColumns(WS:=wsOld, _
                          HeadingsTexts:=msaHeadings1, _
                          HeadingsColumns:=miaHeadingCols1, _
                          HeadingRow:=lHeadingRow1, _
                          KeyColumns:=miaKeyFields1) = False Then
    Exit Sub
End If

Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrDupKey1)

miMaxColumns = UBound(msaHeadings1) + 1
Set objDictOld = PopulateDictionary(WS:=wsOld, _
                                    KeyColumns:=miaKeyFields1, _
                                    HeadingRow:=lHeadingRow1, _
                                    ReportSheet:=mwsReportSheet, _
                                    ColumnPositions:=miaHeadingCols1)
If objDictOld Is Nothing Then
    Exit Sub
End If

On Error Resume Next
mwbOld.Close savechanges:=False
On Error GoTo 0

Set wsNew = GetWorksheet(WSName:=msaCompareSheets(1), WB:=mwbNew)
If wsNew Is Nothing Then Exit Sub

If PopulateHeadingColumns(WS:=wsNew, _
                          HeadingsTexts:=msaHeadings2, _
                          HeadingsColumns:=miaHeadingCols2, _
                          HeadingRow:=lHeadingRow2, _
                          KeyColumns:=miaKeyFields2) = False Then
    Exit Sub
End If

Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrDupKey2)
Set objDictNew = PopulateDictionary(WS:=wsNew, _
                                    KeyColumns:=miaKeyFields2, _
                                    HeadingRow:=lHeadingRow2, _
                                    ReportSheet:=mwsReportSheet, _
                                    ColumnPositions:=miaHeadingCols2)
If objDictNew Is Nothing Then
    Exit Sub
End If

On Error Resume Next
mwbNew.Close savechanges:=False
On Error GoTo 0

vKeys = objDictOld.Keys
For Each vKey In vKeys
    ReDim vaInputOld(1 To 1, 1 To miMaxColumns + 1)     ' Mar 2017
    vaInputOld = objDictOld.Item(vKey)
    If objDictNew.Exists(vKey) Then
        ReDim vaInputNew(1 To 1, 1 To miMaxColumns + 1) ' Mar 2017
        vaInputNew = objDictNew.Item(vKey)
        ReDim vaOutput(1 To 1, 1 To miMaxColumns + 1)
        ReDim vaOutput2(1 To 1, 1 To miMaxColumns + 1)
        ReDim baChanged(1 To miMaxColumns)
        bChanged = False
        For iCol = 1 To miMaxColumns
            vaOutput(1, iCol + 1) = vaInputOld(1, iCol)
            sCompareString1 = AdjustStringForComparison(InputString:=vaInputOld(1, iCol))
            sCompareString2 = AdjustStringForComparison(InputString:=vaInputNew(1, iCol))
            If sCompareString1 <> sCompareString2 Then
                vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
                If mbaHeadingsInfo(iCol - 1) = False Then
                    baChanged(iCol) = True
                    bChanged = True
                End If
            Else
                If mbShowUnchangedCells = True Then
                    vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
                End If
            End If
        Next iCol
        
        If bChanged Then
            Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrMismatched)
            If Not (mwsReportSheet Is Nothing) Then
                mlReportRow = mwsReportSheet.CustomProperties.Item(1).Value
                mlReportRow = mlReportRow + 1
                For iCol = 1 To UBound(baChanged)
                    If baChanged(iCol) Then
                        With mwsReportSheet
                            .Range(.Cells(mlReportRow, iCol + 1).Address, _
                                   .Cells(mlReportRow + 1, iCol + 1).Address).Interior.Color = vbYellow
                        End With
                    End If
                Next iCol
            
                vaOutput(1, 1) = "Changed: Row " & vaInputOld(1, UBound(vaInputOld, 2))
                vaOutput2(1, 1) = "_______:  Row " & vaInputNew(1, UBound(vaInputNew, 2))
                
                With mwsReportSheet
                    .Range(.Cells(mlReportRow, 1).Address, _
                           .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput
                    mlReportRow = mlReportRow + 1
                    .Range(.Cells(mlReportRow, 1).Address, _
                           .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput2
                End With
                
                mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
                
            End If              'If Not (mwsReportSheet Is Nothing) Then
            
        Else                    'If bChanged Then
        
            Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrMatched)
            If Not (mwsReportSheet Is Nothing) Then
                mlReportRow = mwsReportSheet.CustomProperties.Item(1).Value
                mlReportRow = mlReportRow + 1
                mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
                
                vaOutput(1, 1) = "No Change: Row " & vaInputOld(1, UBound(vaInputOld, 2)) & _
                                 ", Row " & vaInputNew(1, UBound(vaInputNew, 2))
                With mwsReportSheet
                    .Range(.Cells(mlReportRow, 1).Address, _
                           .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput
                End With
            End If
        End If                  'If bChanged Then
        
        objDictOld.Remove vKey
        objDictNew.Remove vKey
    Else                            'If objDictNew.Exists(vKey) Then
        Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrData1Only)
        If Not (mwsReportSheet Is Nothing) Then
            mlReportRow = mwsReportSheet.CustomProperties.Item(1)
            mlReportRow = mlReportRow + 1
            mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
            ReDim vaOutput(1 To 1, 1 To miMaxColumns + 1)
            vaOutput(1, 1) = "Only " & msaCompareSheets(0) & " Row " & vaInputOld(1, UBound(vaInputOld, 2))
            For iCol = 1 To miMaxColumns
                vaOutput(1, iCol + 1) = vaInputOld(1, iCol)
            Next iCol
            
            With mwsReportSheet
                .Range(.Cells(mlReportRow, 1).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput
                '-- Set the row to light grey
                .Range(.Cells(mlReportRow, 2).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Interior.ColorIndex = 15
            End With
        End If
    End If                          'If objDictNew.Exists(vKey) Then
Next vKey

If objDictNew.Count <> 0 Then
    vKeys = objDictNew.Keys
    For Each vKey In vKeys
        Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrData2Only)
        If Not (mwsReportSheet Is Nothing) Then
            mlReportRow = mwsReportSheet.CustomProperties.Item(1)
            mlReportRow = mlReportRow + 1
            mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
            ReDim vaOutput2(1 To 1, 1 To miMaxColumns + 1)
            vaInputNew = objDictNew.Item(vKey)
            vaOutput2(1, 1) = "Only " & msaCompareSheets(1) & " Row " & vaInputNew(1, UBound(vaInputNew, 2))
            For iCol = 1 To miMaxColumns
                vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
            Next iCol
            With mwsReportSheet
                .Range(.Cells(mlReportRow, 1).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput2
                '-- Set the row to light green
                .Range(.Cells(mlReportRow, 2).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Interior.ColorIndex = 4
            End With
        End If
    Next vKey
End If


For lPtr = 1 To UBound(mwsaResultsSheets)
    Set mwsReportSheet = mwsaResultsSheets(lPtr)
    SetResultsSheetColumnWidths WS:=mwsReportSheet
Next lPtr

objDictOld.RemoveAll
Set objDictOld = Nothing
objDictNew.RemoveAll
Set objDictNew = Nothing
End Sub

Private Sub SetResultsSheetColumnWidths(ByVal WS As Worksheet)
Dim lEndCol As Long
Dim saColumns() As String

On Error GoTo 0
If WS Is Nothing Then
Else
    WS.Calculate
    WS.Columns("A:A").ColumnWidth = 30
    lEndCol = WS.UsedRange.Columns.Count
    saColumns = Split(WS.Cells(1, lEndCol).Address(True, True), "$")
    WS.Columns("B:" & saColumns(1)).EntireColumn.AutoFit
End If

End Sub

Private Function AdjustStringForComparison(ByVal InputString As String) As String
Dim lPtr As Long

Dim sChar As String
Dim sResult As String

If mbIgnoreCase = True Then
    InputString = LCase$(InputString)
End If

If Len(msOnlyCharacters) = 0 Then
    sResult = InputString
Else
    If mbIgnoreCase = True Then
        msOnlyCharacters = LCase$(msOnlyCharacters)
    End If
    For lPtr = 1 To Len(InputString)
        sChar = Mid$(InputString, lPtr, 1)
        If InStr(msOnlyCharacters, sChar) > 0 Then
            sResult = sResult & sChar
        End If
    Next lPtr
End If

If Len(msIgnoreCharacters) > 0 Then
    If mbIgnoreCase = True Then
        msIgnoreCharacters = LCase$(msIgnoreCharacters)
    End If
    For lPtr = 1 To Len(msIgnoreCharacters)
        sChar = Mid$(msIgnoreCharacters, lPtr, 1)
        sResult = Replace(sResult, sChar, "")
    Next lPtr
End If

AdjustStringForComparison = sResult

End Function

Private Function GetResultsWorksheet(ByVal WSName As String) As Worksheet
Dim lSheetsCount As Long
Dim sWSNumber As String

If Replace(LCase$(WSName), " ", "") = "<<no>>" Then
    Set GetResultsWorksheet = Nothing
Else
    On Error Resume Next
    Set GetResultsWorksheet = ThisWorkbook.Sheets(WSName)
    On Error GoTo 0
    If (GetResultsWorksheet Is Nothing) Then
        lSheetsCount = ThisWorkbook.Sheets.Count
        With ThisWorkbook
            lSheetsCount = .Sheets.Count
            Set GetResultsWorksheet = .Sheets.Add(after:=.Sheets(lSheetsCount))
        End With
        On Error Resume Next
        Err.Number = 0
        GetResultsWorksheet.Name = WSName
        If Err.Number > 0 Then
            MsgBox prompt:="Invalid sheet name '" & WSName & "'. Data being sent to sheet '" & _
                            GetResultsWorksheet.Name & "'", _
                    Buttons:=vbOKOnly + vbExclamation
        End If
    End If
    If Not (GetResultsWorksheet Is Nothing) Then
        On Error Resume Next
        With GetResultsWorksheet.CustomProperties
            .Item(1).Value = 0
            .Add Name:="LastRowUsed", Value:=0
        End With
    End If
End If
End Function

Private Function GetWorksheet(ByVal WSName As String, ByRef WB As Workbook) As Worksheet
Dim bThisWB As Boolean
Dim iWSPtr As Integer
Dim saWorksheet() As String
Dim WBInput As Workbook

Set GetWorksheet = Nothing
saWorksheet = Split(WSName, "!")
iWSPtr = UBound(saWorksheet)

Set WBInput = Nothing
On Error Resume Next
If iWSPtr = 0 Then
    Set WBInput = ThisWorkbook
    bThisWB = True
Else
    bThisWB = False
    Set WBInput = Workbooks.Open(Filename:=ThisWorkbook.Path & "\" & saWorksheet(0), ReadOnly:=True)
    Set WB = WBInput
End If
If WBInput Is Nothing Then
    MsgBox prompt:="Unable to open '" & saWorksheet(0), Buttons:=vbOKOnly + vbCritical
    Exit Function
End If

Set GetWorksheet = WBInput.Sheets(saWorksheet(iWSPtr))
If GetWorksheet Is Nothing Then
    If bThisWB = False Then WBInput.Close savechanges:=False
    MsgBox prompt:="Unable to access worksheet '" & WSName & "'", Buttons:=vbOKOnly + vbCritical
End If
End Function
Private Function PopulateDictionary(ByRef WS As Worksheet, _
                                    ByRef KeyColumns() As Integer, _
                                    ByVal HeadingRow As Long, _
                                    ByVal ReportSheet As Worksheet, _
                                    ByRef ColumnPositions() As Integer) As Object
Dim iPtr As Integer
Dim iKeyColsPtr As Integer
Dim iKeyPtr As Integer
Dim iCurCol As Integer
Dim iColEnd As Integer

Dim lRowEnd As Long
Dim lRow As Long
Dim lErrorCount As Long
Dim lReportPtr As Long

Dim rCur As Range

Dim sKey As String
Dim sCurKey As String
Dim sText As String
Dim saCurKey() As String

Dim vaItem() As Variant
Dim vaCurRow As Variant
Dim vaReport As Variant
Dim vReply As Variant

ReDim saCurKey(LBound(KeyColumns) To UBound(KeyColumns))

With WS.UsedRange
    iColEnd = .Column + .Columns.Count - 1
End With

Set PopulateDictionary = Nothing
Set PopulateDictionary = CreateObject("Scripting.Dictionary")
lRowEnd = WS.Cells(Rows.Count, ColumnPositions(0)).End(xlUp).Row
For lRow = HeadingRow + 1 To lRowEnd
    vaCurRow = WS.Range("A" & lRow).Resize(, iColEnd).Value
    sKey = ""
    For iKeyColsPtr = LBound(KeyColumns) To UBound(KeyColumns)
        iKeyPtr = KeyColumns(iKeyColsPtr)
        If iKeyPtr <> 0 Then
            saCurKey(iKeyColsPtr) = CStr(vaCurRow(1, iKeyPtr))
            sCurKey = LCase$(CStr(vaCurRow(1, iKeyPtr)))
            If mbFilterKey = True Then
                sCurKey = AdjustStringForComparison(sCurKey)
            End If
            sKey = sKey & "|" & sCurKey
        End If
    Next iKeyColsPtr
    If sKey = "" Then
        MsgBox prompt:="No key headings specified", _
                Buttons:=vbOKOnly + vbCritical, _
                Title:="PARAMETER ERROR"
        Set PopulateDictionary = Nothing
        Exit Function
    End If
    sKey = Mid$(sKey, 2)
    
    ReDim vaItem(1 To 1, 1 To UBound(ColumnPositions) + 2)
    For iPtr = 0 To UBound(ColumnPositions)
        iCurCol = ColumnPositions(iPtr)
        vaItem(1, iPtr + 1) = vaCurRow(1, iCurCol)
    Next iPtr
    vaItem(1, UBound(vaItem, 2)) = lRow         '-- Add row number to last element --
    
    If PopulateDictionary.Exists(sKey) Then
    
        If Not (ReportSheet Is Nothing) Then
            lDuplicateCount = lDuplicateCount + 1
            sText = "Duplicate key at row " & lRow & " of " & WS.Parent.Name & "!" & WS.Name & "."
            
            ReDim vaReport(1 To 1, 1 To UBound(vaItem, 2))
            vaReport(1, 1) = sText
            For lReportPtr = 1 To UBound(vaReport, 2) - 1
                vaReport(1, lReportPtr + 1) = vaItem(1, lReportPtr)
            Next lReportPtr

            mlReportRow = ReportSheet.CustomProperties.Item(1)
            mlReportRow = mlReportRow + 1
            ReportSheet.CustomProperties.Item(1).Value = mlReportRow
            
            With ReportSheet.Range("A" & mlReportRow).Resize(, UBound(vaReport, 2))
                .Value = vaReport
                .Characters.Font.Color = vbRed
            End With
        End If
        
    Else
        On Error Resume Next
        PopulateDictionary.Add Key:=sKey, Item:=vaItem
        If Err.Number <> 0 Then
            If MsgBox(prompt:="Error " & Err.Number & " in sheet " & WS.Name & " row " & lRow & vbCrLf & _
                                    Err.Description & vbCrLf & "Do you wish to ignore this and  continue?", _
                            Buttons:=vbYesNo + vbCritical, _
                            Title:="ERROR DETECTED") = vbNo Then
                Set PopulateDictionary = Nothing
                Exit Function
            End If
        End If
        On Error GoTo 0
    End If
Next lRow
End Function

Private Function GetParameters() As Boolean
Dim bError As Boolean
Dim iKeyFieldCount As Integer
Dim iPtr As Integer
Dim iParamCheck As Integer
Const iParamCompareSheets As Integer = 1
Const iParamResultsSheet As Integer = 2
Const iParamHeadings As Integer = 4

Dim lRow As Long
Dim sCurKey As String
Dim saCurInput() As String
Dim saHeadings() As String, saHeadingsA() As String
Dim vaParameters As Variant
Dim vaArrayResultsParams As Variant

Dim wsParams As Worksheet, wsTemp As Worksheet

On Error Resume Next
For iPtr = 1 To UBound(mwsaResultsSheets)
    Set mwsaResultsSheets(iPtr) = Nothing
Next iPtr
On Error GoTo 0

Set wsParams = Nothing
On Error Resume Next
Set wsParams = Sheets("Parameters")
On Error GoTo 0
If wsParams Is Nothing Then
    MsgBox prompt:="Cannot access 'Parameters' sheet", _
            Buttons:=vbOKOnly + vbCritical, _
            Title:="ERROR"
    GetParameters = False
    Exit Function
End If

lRow = wsParams.Cells(Rows.Count, "A").End(xlUp).Row
vaParameters = wsParams.Range("A1:B" & lRow).Value

ReDim msaHeadingRows(0 To 0)
msaHeadingRows(0) = "1"

mbDisplayOutputHeadings = True

iParamCheck = 0
For lRow = 2 To UBound(vaParameters, 1)
    sCurKey = NormaliseText(CStr(vaParameters(lRow, 1)))
    Select Case sCurKey
    Case "comparesheets"
        iParamCheck = iParamCheck Or iParamCompareSheets
        msaCompareSheets = Split(CStr(vaParameters(lRow, 2)), ",")
        If UBound(msaCompareSheets) <> 1 Then
            MsgBox prompt:="'Compare Sheets' parameter must have exactly two elements", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End If
        
        For iPtr = 0 To 1
            msaCompareSheets(iPtr) = Trim$(msaCompareSheets(iPtr))
            bError = msaCompareSheets(iPtr) = ""
            If bError = False Then
                saCurInput = Split(msaCompareSheets(iPtr), "!")
                bError = UBound(saCurInput) > 1
            End If
            If bError Then
                MsgBox prompt:="'Compare Sheets' parameter error", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
            End If
            
        Next iPtr
        
    Case "resultssheetduplicatekeydata1"
        Set mwsaResultsSheets(mlResultsPtrDupKey1) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        
    Case "resultssheetduplicatekeydata2"
        Set mwsaResultsSheets(mlResultsPtrDupKey2) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
    
    Case "resultssheetmismatched"
        Set mwsaResultsSheets(mlResultsPtrMismatched) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
    
    Case "resultssheetmatched"
        Set mwsaResultsSheets(mlResultsPtrMatched) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
    
    Case "resultssheetdata1only"
        Set mwsaResultsSheets(mlResultsPtrData1Only) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
    
    Case "resultssheetdata2only"
        Set mwsaResultsSheets(mlResultsPtrData2Only) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        
    Case "headingsrow"
        msaHeadingRows = Split(CStr(vaParameters(lRow, 2)), ",")
        
    Case "headings"
        iParamCheck = iParamCheck Or iParamHeadings
        saHeadings = Split(CStr(vaParameters(lRow, 2)), ",")
        
        ReDim msaHeadings1(0 To UBound(saHeadings))
        ReDim msaHeadings2(0 To UBound(saHeadings))
        ReDim miaHeadingCols1(0 To UBound(saHeadings))
        ReDim miaKeyFields1(0 To UBound(saHeadings))
        ReDim miaKeyFields2(0 To UBound(saHeadings))
        ReDim miaHeadingCols2(0 To UBound(saHeadings))
        ReDim mbaHeadingsInfo(0 To UBound(saHeadings))
        ReDim mbaKeyFields(0 To UBound(saHeadings))
        iKeyFieldCount = 0
        
        For iPtr = 0 To UBound(saHeadings)
            saHeadingsA = Split("=" & saHeadings(iPtr), "=")
            If UBound(saHeadingsA) < 1 Or UBound(saHeadingsA) > 2 Then
                MsgBox prompt:="Invalid headings value", Buttons:=vbOKOnly + vbCritical
                GetParameters = False
                Exit Function
            End If
            ReDim Preserve saHeadingsA(0 To 2)
            saHeadingsA(1) = Trim$(saHeadingsA(1))
            mbaHeadingsInfo(iPtr) = LCase$(Left$(saHeadingsA(1) & "123456", 6)) = "(info)"
            If mbaHeadingsInfo(iPtr) Then saHeadingsA(1) = Mid$(saHeadingsA(1), 7)
            mbaKeyFields(iPtr) = LCase$(Left$(saHeadingsA(1) & "12345", 5)) = "(key)"
            If mbaKeyFields(iPtr) Then
                iKeyFieldCount = iKeyFieldCount + 1
                saHeadingsA(1) = Mid$(saHeadingsA(1), 6)
            End If
            If saHeadingsA(2) = "" Then saHeadingsA(2) = saHeadingsA(1)
            msaHeadings1(iPtr) = saHeadingsA(1)
            msaHeadings2(iPtr) = Trim$(saHeadingsA(2))
        Next iPtr
        If iKeyFieldCount = 0 Then
            MsgBox prompt:="No key fields specified", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End If
        
    Case "displayoutputheadings"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbDisplayOutputHeadings = True
        Case "no"
            mbDisplayOutputHeadings = False
        Case Else
            MsgBox prompt:="'Display Output Headings' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
    
    Case "ignorecase"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbIgnoreCase = True
        Case "no"
            mbIgnoreCase = False
        Case Else
            MsgBox prompt:="'Ignore Case' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
                
    Case "filterkey"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbFilterKey = True
        Case "no"
            mbFilterKey = False
        Case Else
            MsgBox prompt:="'Filter Key' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
                       
    Case "ignorecharacters"
        msIgnoreCharacters = CStr(vaParameters(lRow, 2))
        
    Case "onlycharacters"
        msOnlyCharacters = CStr(vaParameters(lRow, 2))
    
    Case "showunchangedcells"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbShowUnchangedCells = True
        Case "no"
            mbShowUnchangedCells = False
        Case Else
            MsgBox prompt:="'Show Unchanged Cells' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
        
    Case Else
        MsgBox prompt:="Unrecognised parameter in row " & lRow, Buttons:=vbOKOnly + vbCritical
        GetParameters = False
        Exit Function
    End Select
Next lRow

GetParameters = True

'vaArrayResultsParams = Array("Results Sheet Duplicate Key Data 1", _
'                             "Results Sheet Duplicate Key Data 2", _
'                             "Results Sheet Mismatched", _
'                             "Results Sheet Matched", _
'                             "Results Sheet Data 1 Only", _
'                             "Results Sheet Data 2 Only")
'
'For lRow = 1 To UBound(mwsaResultsSheets)
'    If (mwsaResultsSheets(lRow) Is Nothing) Then
'        MsgBox prompt:="Parameter " & vaArrayResultsParams(lRow - 1) & " has an invalid value"
'        GetParameters = False
'    End If
'Next lRow

End Function

Public Function StartsWith(str As String, prefix As String) As Boolean
    StartsWith = Left(str, Len(prefix)) = prefix
End Function

Private Function PopulateHeadingColumns(ByVal WS As Worksheet, _
                                        ByRef HeadingsTexts() As String, _
                                        ByRef HeadingsColumns() As Integer, _
                                        ByVal HeadingRow As Long, _
                                        ByRef KeyColumns() As Integer) As Boolean
Dim bFound As Boolean
Dim iPtrCol As Integer, iPtrHeading As Integer, iColEnd As Integer
Dim sCurHeading As String, sCur As String
Dim vaHeadings() As Variant

iColEnd = WS.Cells(HeadingRow, Columns.Count).End(xlToLeft).Column
vaHeadings = WS.Range("A" & HeadingRow & ":" & WS.Cells(HeadingRow, iColEnd).Address).Value

For iPtrHeading = LBound(HeadingsTexts) To UBound(HeadingsTexts)
    sCurHeading = NormaliseText(HeadingsTexts(iPtrHeading))
    bFound = False
    For iPtrCol = 1 To UBound(vaHeadings, 2)
        If sCurHeading = NormaliseText(CStr(vaHeadings(1, iPtrCol))) Then
            HeadingsColumns(iPtrHeading) = iPtrCol
            If mbaKeyFields(iPtrHeading) = True Then KeyColumns(iPtrHeading) = iPtrCol
            bFound = True
            Exit For
        End If
    Next iPtrCol
    If bFound = False Then
        MsgBox prompt:="Heading '" & HeadingsTexts(iPtrHeading) & "' not found in sheet '" & WS.Name, _
                Buttons:=vbOKOnly + vbCritical
                PopulateHeadingColumns = False
                Exit Function
    End If
Next iPtrHeading

PopulateHeadingColumns = True
End Function

Private Function NormaliseText(ByVal TextString As String) As String
'-- Convert to lower case and remove all but alphanumerics --
Dim iPtr As Integer
Dim sHold As String
Dim sChar As String
Dim sResult As String

sHold = Replace(LCase$(TextString), " ", "")
sResult = ""
For iPtr = 1 To Len(sHold)
    sChar = Mid$(sHold, iPtr, 1)
    If IsNumeric(sChar) Or sChar <> UCase$(sChar) Then
        sResult = sResult & sChar
    End If
Next iPtr
NormaliseText = sResult
End Function


Excel 2013 32 bit
ABC
1KeywordValueComment
2Compare SheetsCompare Sheets Test Data 1!Sheet1,Compare Sheets Test Data 2!Sheet1Sheets to be compared. Sheet names may optionally be prefixed by a workbook name (Without the extension) followed by "!", e.g. "OldWorkbook!old" or "c:\otherfolder\otherworkbook!sheet1"
3Results Sheet Duplicate Key Data 1Duplicate KeysSheet to contain Duplicate keys from Data 1. If "<>", results will not be output. If the sheet does not exist, it will be created.
4Results Sheet Duplicate Key Data 2Duplicate KeysSheet to contain Duplicate keys from Data 2. If "<>", results will not be output. If the sheet does not exist, it will be created.
5Results Sheet MismatchedMismatchedSheet to contain Changed rows from both sheets (along with matched data and highlight only mismatched cells). If "<>", results will not be output. If the sheet does not exist, it will be created.
6Results Sheet MatchedMatchedSheet to contain Matched rows from Data 2 (alike duplicate report). If "<>", results will not be output. If the sheet does not exist, it will be created.
7Results Sheet Data 1 OnlyUniqueSheet to contain rows appearing in Data 1 Only. If "<>", results will not be output. If the sheet does not exist, it will be created.
8Results Sheet Data 2 OnlyUniqueSheet to contain rows appearing in Data 2 Only. If "<>", results will not be output. If the sheet does not exist, it will be created.
9Show Unchanged CellsYesYes or No. If "No", for mismatched records, matching cells in Data 2 will be shown as blank.
10Headings Row1Row Number containing Headings.
11Display Output HeadingsYesYes or No. If this parameter is absent, "Yes" is assumed
12Ignore CaseYesYes or No
13Ignore Characters-Characters to be removed before comparison
14Only Charactersif not blank, characters to be compared - any other chars not in this list will be removed before comparison
15Filter KeyYesYes or No. if Yes, "Ignore Characters" and "Only Characters" parameters are applied to key column(s) Note that "Ignore Case" is applied irrespective.
16Headings(Key)Forename=First Name, (key)Surname=Last Name, Address Line 1=Address , Post Town, Post Code, (info)CommentList of headings to be compared / displayed, seperated by a comma. The format of each heading definition comprises the following elements:
17> an optional Heading descriptor which is one of "(key)" or "(info)"
18"(key)" indicates that the fields under the heading definition is part of the record key. At least one "(key)" heading must be present.
19"(info)" indicates that the field is to be displayed, but is not to be part of the comparison.
20> Optionally an equals followed by the heading from the second sheet against which the field is to be compared. If not specified, it is assumed that the headings from both sheets are the same.
Parameters
 
Upvote 0

Excel Facts

Who is Mr Spreadsheet?
Author John Walkenbach was Mr Spreadsheet until his retirement in June 2019.
OMG Superbbbbb!!! :)

Hi Alan
This is what EXACTLY I was looking for and one time thought could not be possible!!! For me it is not less than miracle. You can do anything possible.
Thank you so muchhhhhhh :) God bless you.

Many Many Thanks
Nil
 
Upvote 0
Hi Alan,
There are many line items in Matched Sheet appearing due to special characters. Can you please suggest code for highlighting those line items with some color?

Thanks,
Nil
 
Last edited:
Upvote 0
Hi Nil, do you want to ignore those special characters in the comparison? We could either use the "Clean" function to get rid of them or we could specify a list of permitted characters, all others to be stripped out before comparing.
 
Upvote 0
Hi Alan,
In case of match cases, I want to highlight line items where data has been matched after ignoring special characters. i.e. Post Town in Data1 is 'Post-Town-11' & in Data2 is 'Post Town 11', then it will appear in Output (Matched) sheet. However I want to highlight this line with some color.

Regards,
Nil
 
Upvote 0
Hi Nil, have amended the code to enable you to specify the format of the output via the parameter sheet:

Excel 2012
ABC
1KeywordValueComment
2Compare SheetsCompare Sheets Test Data 1!Sheet1,Compare Sheets Test Data 2!Sheet1Sheets to be compared. Sheet names may optionally be prefixed by a workbook name (Without the extension) followed by "!", e.g. "OldWorkbook!old" or "c:\otherfolder\otherworkbook!sheet1"
3Results Sheet Duplicate Key Data 1Duplicate KeysSheet to contain Duplicate keys from Data 1. If "<>", results will not be output. If the sheet does not exist, it will be created.
4Results Sheet Duplicate Key Data 2Duplicate KeysSheet to contain Duplicate keys from Data 2. If "<>", results will not be output. If the sheet does not exist, it will be created.
5Results Sheet MismatchedMismatchedSheet to contain Changed rows from both sheets (along with matched data and highlight only mismatched cells). If "<>", results will not be output. If the sheet does not exist, it will be created.
6Results Sheet MatchedMatchedSheet to contain Matched rows from Data 2 (alike duplicate report). If "<>", results will not be output. If the sheet does not exist, it will be created.
7Results Sheet Data 1 OnlyUniqueSheet to contain rows appearing in Data 1 Only. If "<>", results will not be output. If the sheet does not exist, it will be created.
8Results Sheet Data 2 OnlyUniqueSheet to contain rows appearing in Data 2 Only. If "<>", results will not be output. If the sheet does not exist, it will be created.
9Show Unchanged CellsYesYes or No. If "No", for mismatched records, matching cells in Data 2 will be shown as blank.
10Headings Row1Row Number containing Headings.
11Display Output HeadingsYesYes or No. If this parameter is absent, "Yes" is assumed
12Ignore CaseYesYes or No
13Ignore Characters-Characters to be removed before comparison
14Only Charactersif not blank, characters to be compared - any other chars not in this list will be removed before comparison
15Filter KeyYesYes or No. if Yes, "Ignore Characters" and "Only Characters" parameters are applied to key column(s) Note that "Ignore Case" is applied irrespective.
16Headings(Key)Forename=First Name, (key)Surname=Last Name, Address Line 1=Address , Post Town, Post Code, (info)CommentList of headings to be compared / displayed, seperated by a comma. The format of each heading definition comprises the following elements:
17> an optional Heading descriptor which is one of "(key)" or "(info)"
18"(key)" indicates that the fields under the heading definition is part of the record key. At least one "(key)" heading must be present.
19"(info)" indicates that the field is to be displayed, but is not to be part of the comparison.
20> Optionally an equals followed by the heading from the second sheet against which the field is to be compared. If not specified, it is assumed that the headings from both sheets are the same.
Parameters


The code:
Code:
Dim mbaKeyFields() As Boolean
Dim mbaKeyCols1() As Boolean
Dim mbaKeyCols2() As Boolean
Dim mbaHeadingsInfo() As Boolean
Dim mbIgnoreCase As Boolean
Dim mbDisplayOutputHeadings As Boolean
Dim mbFilterKey As Boolean
Dim mbShowUnchangedCells As Boolean

Dim miMaxColumns As Integer
Dim miaHeadingCols1() As Integer
Dim miaHeadingCols2() As Integer
Dim miaKeyFields1() As Integer
Dim miaKeyFields2() As Integer

Const mlResultsPtrDupKey1 As Long = 1
Const mlResultsPtrDupKey2 As Long = 2
Const mlResultsPtrMismatched As Long = 3
Const mlResultsPtrMatched As Long = 4
Const mlResultsPtrData1Only As Long = 5
Const mlResultsPtrData2Only As Long = 6

Dim mlReportRow As Long
Dim mlaResultsSheetsPtrs(1 To 6) As Long

Dim mrFormatDupKey1 As Range
Dim mrFormatDupKey2 As Range
Dim mrFormatMismatched As Range
Dim mrFormatMatched As Range
Dim mrFormatData1Only As Range
Dim mrFormatData2Only As Range

Dim msIgnoreCharacters As String
Dim msOnlyCharacters As String
Dim msaCompareSheets() As String
Dim msResultsSheet As String
Dim msaHeadings1() As String
Dim msaHeadings2() As String
Dim msaHeadingRows() As String
Dim msaResultsSheets(1 To 6) As String

Dim mvaDuplicateKeys As Variant

Dim mwbOld As Workbook
Dim mwbNew As Workbook

Dim mwsReportSheet As Worksheet
Dim mwsaResultsSheets(1 To 6) As Worksheet

Sub CompareSheets()
Dim bChanged As Boolean
Dim baChanged() As Boolean
Dim bIgnoreChanged As Boolean
Dim baIgnoreChanged() As Boolean

Dim iColEnd As Integer, iCol As Integer, iCol1 As Integer, iCol2 As Integer

Dim lSheetPtr As Long
Dim lRow1 As Long
Dim lRow2 As Long
Dim lHeadingRow1 As Long
Dim lHeadingRow2 As Long

Dim objDictOld As Object, objDictNew As Object

Dim sCompareString1 As String
Dim sCompareString2 As String

Dim vKeys As Variant, vKey As Variant
Dim vaInput() As Variant, vaOutput() As Variant, vaOutput2() As Variant
Dim vaInputOld As Variant, vaInputNew As Variant
Dim vaHeadings() As Variant

Dim wsOld As Worksheet, wsNew As Worksheet

mlDuplicateKeysCount = 0
mlReportRow = 0

Application.ScreenUpdating = False

If GetParameters = False Then Exit Sub

For lSheetPtr = 1 To UBound(mwsaResultsSheets)
    Set mwsReportSheet = mwsaResultsSheets(lSheetPtr)
    If Not (mwsReportSheet Is Nothing) Then
        With mwsReportSheet.Cells
            .ClearFormats
            .ClearContents
        End With
        If mbDisplayOutputHeadings = True Then
            ReDim vaHeadings(1 To 1, 1 To UBound(msaHeadings1) + 2)
            For iCol = 0 To UBound(msaHeadings1)
                If msaHeadings1(iCol) = msaHeadings2(iCol) Then
                    vaHeadings(1, iCol + 2) = msaHeadings1(iCol)
                Else
                    vaHeadings(1, iCol + 2) = msaHeadings1(iCol) & " / " & msaHeadings2(iCol)
                End If
            Next iCol
            mwsReportSheet.Range("A1", mwsReportSheet.Cells(1, UBound(vaHeadings, 2)).Address).Value = vaHeadings
            mwsReportSheet.CustomProperties.Item(1).Value = 1
        Else
            mwsReportSheet.CustomProperties.Item(1).Value = 0
        End If
    End If
Next lSheetPtr

lHeadingRow1 = Val(msaHeadingRows(0))
If lHeadingRow1 < 1 Then lHeadingRow1 = 1

lHeadingRow2 = Val(msaHeadingRows(UBound(msaHeadingRows)))
If lHeadingRow2 < 1 Then lHeadingRow2 = 1

Set wsOld = GetWorksheet(WSName:=msaCompareSheets(0), WB:=mwbOld)
If wsOld Is Nothing Then Exit Sub

If PopulateHeadingColumns(WS:=wsOld, _
                          HeadingsTexts:=msaHeadings1, _
                          HeadingsColumns:=miaHeadingCols1, _
                          HeadingRow:=lHeadingRow1, _
                          KeyColumns:=miaKeyFields1) = False Then
    Exit Sub
End If

Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrDupKey1)

miMaxColumns = UBound(msaHeadings1) + 1
Set objDictOld = PopulateDictionary(WS:=wsOld, _
                                    KeyColumns:=miaKeyFields1, _
                                    HeadingRow:=lHeadingRow1, _
                                    ReportSheet:=mwsReportSheet, _
                                    ColumnPositions:=miaHeadingCols1, _
                                    DupFormatRange:=mrFormatDupKey1)
If objDictOld Is Nothing Then
    Exit Sub
End If

On Error Resume Next
mwbOld.Close savechanges:=False
On Error GoTo 0

Set wsNew = GetWorksheet(WSName:=msaCompareSheets(1), WB:=mwbNew)
If wsNew Is Nothing Then Exit Sub

If PopulateHeadingColumns(WS:=wsNew, _
                          HeadingsTexts:=msaHeadings2, _
                          HeadingsColumns:=miaHeadingCols2, _
                          HeadingRow:=lHeadingRow2, _
                          KeyColumns:=miaKeyFields2) = False Then
    Exit Sub
End If

Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrDupKey2)
Set objDictNew = PopulateDictionary(WS:=wsNew, _
                                    KeyColumns:=miaKeyFields2, _
                                    HeadingRow:=lHeadingRow2, _
                                    ReportSheet:=mwsReportSheet, _
                                    ColumnPositions:=miaHeadingCols2, _
                                    DupFormatRange:=mrFormatDupKey2)
If objDictNew Is Nothing Then
    Exit Sub
End If

On Error Resume Next
mwbNew.Close savechanges:=False
On Error GoTo 0

vKeys = objDictOld.Keys
For Each vKey In vKeys
    ReDim vaInputOld(1 To 1, 1 To miMaxColumns + 1)     ' Mar 2017
    vaInputOld = objDictOld.Item(vKey)
    If objDictNew.Exists(vKey) Then
        ReDim vaInputNew(1 To 1, 1 To miMaxColumns + 1) ' Mar 2017
        vaInputNew = objDictNew.Item(vKey)
        ReDim vaOutput(1 To 1, 1 To miMaxColumns + 1)
        ReDim vaOutput2(1 To 1, 1 To miMaxColumns + 1)
        ReDim baChanged(1 To miMaxColumns)
        ReDim baIgnoreChanged(1 To miMaxColumns + 1)
        
        bChanged = False
        For iCol = 1 To miMaxColumns
            vaOutput(1, iCol + 1) = vaInputOld(1, iCol)
            sCompareString1 = AdjustStringForComparison(InputString:=vaInputOld(1, iCol))
            sCompareString2 = AdjustStringForComparison(InputString:=vaInputNew(1, iCol))
            baIgnoreChanged(iCol) = CStr(LCase$(vaInputOld(1, iCol))) <> CStr(LCase$(vaInputNew(1, iCol)))
            bIgnoreChanged = bIgnoreChanged Or baIgnoreChanged(iCol)
            
            If sCompareString1 <> sCompareString2 Then
                vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
                If mbaHeadingsInfo(iCol - 1) = False Then
                    baChanged(iCol) = True
                    bChanged = True
                End If
            Else
                If mbShowUnchangedCells = True Then
                    vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
                End If
            End If
        Next iCol
        
        If bChanged Then
            Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrMismatched)
            If Not (mwsReportSheet Is Nothing) Then
                mlReportRow = mwsReportSheet.CustomProperties.Item(1).Value
                mlReportRow = mlReportRow + 1
                mrFormatMismatched.Copy
                For iCol = 1 To UBound(baChanged)
                    If baChanged(iCol) Then
                        With mwsReportSheet
                            .Range(.Cells(mlReportRow, iCol + 1).Address, _
                                   .Cells(mlReportRow + 1, iCol + 1).Address).PasteSpecial xlPasteFormats
                        End With
                    End If
                Next iCol
            
                vaOutput(1, 1) = "Changed: Row " & vaInputOld(1, UBound(vaInputOld, 2))
                vaOutput2(1, 1) = "_______:  Row " & vaInputNew(1, UBound(vaInputNew, 2))
                
                With mwsReportSheet
                    .Range(.Cells(mlReportRow, 1).Address, _
                           .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput
                    mlReportRow = mlReportRow + 1
                    .Range(.Cells(mlReportRow, 1).Address, _
                           .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput2
                End With
                
                mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
                
            End If              'If Not (mwsReportSheet Is Nothing) Then
            
        Else                    'If bChanged Then
                
            Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrMatched)
            If Not (mwsReportSheet Is Nothing) Then
                mlReportRow = mwsReportSheet.CustomProperties.Item(1).Value
                mlReportRow = mlReportRow + 1
                mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
                
                If bIgnoreChanged = True Then
                    mrFormatMatched.Copy
                    For iCol = 1 To UBound(baIgnoreChanged)
                        If baIgnoreChanged(iCol) = True Then
                            With mwsReportSheet
                                .Range(.Cells(mlReportRow, iCol + 1).Address).PasteSpecial xlPasteFormats
                            End With
                        End If
                    Next iCol
                End If
                
                vaOutput(1, 1) = "No Change: Row " & vaInputOld(1, UBound(vaInputOld, 2)) & _
                                 ", Row " & vaInputNew(1, UBound(vaInputNew, 2))
                With mwsReportSheet
                    .Range(.Cells(mlReportRow, 1).Address, _
                           .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput
                End With
            End If
        End If                  'If bChanged Then
        
        objDictOld.Remove vKey
        objDictNew.Remove vKey
    Else                            'If objDictNew.Exists(vKey) Then
        Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrData1Only)
        If Not (mwsReportSheet Is Nothing) Then
            mlReportRow = mwsReportSheet.CustomProperties.Item(1)
            mlReportRow = mlReportRow + 1
            mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
            ReDim vaOutput(1 To 1, 1 To miMaxColumns + 1)
            vaOutput(1, 1) = "Only " & msaCompareSheets(0) & " Row " & vaInputOld(1, UBound(vaInputOld, 2))
            For iCol = 1 To miMaxColumns
                vaOutput(1, iCol + 1) = vaInputOld(1, iCol)
            Next iCol
            
            With mwsReportSheet
                .Range(.Cells(mlReportRow, 1).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput
                '-- Set the row format
                mrFormatData1Only.Copy
                .Range(.Cells(mlReportRow, 2).Address, .Cells(mlReportRow, miMaxColumns + 1).Address) _
                    .PasteSpecial xlPasteFormats
            End With
        End If
    End If                          'If objDictNew.Exists(vKey) Then
Next vKey

If objDictNew.Count <> 0 Then
    vKeys = objDictNew.Keys
    For Each vKey In vKeys
        Set mwsReportSheet = mwsaResultsSheets(mlResultsPtrData2Only)
        If Not (mwsReportSheet Is Nothing) Then
            mlReportRow = mwsReportSheet.CustomProperties.Item(1)
            mlReportRow = mlReportRow + 1
            mwsReportSheet.CustomProperties.Item(1).Value = mlReportRow
            ReDim vaOutput2(1 To 1, 1 To miMaxColumns + 1)
            vaInputNew = objDictNew.Item(vKey)
            vaOutput2(1, 1) = "Only " & msaCompareSheets(1) & " Row " & vaInputNew(1, UBound(vaInputNew, 2))
            For iCol = 1 To miMaxColumns
                vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
            Next iCol
            With mwsReportSheet
                .Range(.Cells(mlReportRow, 1).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Value = vaOutput2
                '-- Set the row format
                mrFormatData2Only.Copy
                .Range(.Cells(mlReportRow, 2).Address, .Cells(mlReportRow, miMaxColumns + 1).Address) _
                    .PasteSpecial xlPasteFormats
'                .Range(.Cells(mlReportRow, 2).Address, .Cells(mlReportRow, miMaxColumns + 1).Address).Interior.ColorIndex = 4
            End With
        End If
    Next vKey
End If


For lPtr = 1 To UBound(mwsaResultsSheets)
    Set mwsReportSheet = mwsaResultsSheets(lPtr)
    SetResultsSheetColumnWidths WS:=mwsReportSheet
Next lPtr

objDictOld.RemoveAll
Set objDictOld = Nothing
objDictNew.RemoveAll
Set objDictNew = Nothing
End Sub

Private Sub SetResultsSheetColumnWidths(ByVal WS As Worksheet)
Dim lEndCol As Long
Dim saColumns() As String

On Error GoTo 0
If WS Is Nothing Then
Else
    WS.Calculate
    WS.Columns("A:A").ColumnWidth = 30
    lEndCol = WS.UsedRange.Columns.Count
    saColumns = Split(WS.Cells(1, lEndCol).Address(True, True), "$")
    WS.Columns("B:" & saColumns(1)).EntireColumn.AutoFit
End If

End Sub

Private Function AdjustStringForComparison(ByVal InputString As String) As String
Dim lPtr As Long

Dim sChar As String
Dim sResult As String

If mbIgnoreCase = True Then
    InputString = LCase$(InputString)
End If

If Len(msOnlyCharacters) = 0 Then
    sResult = InputString
Else
    If mbIgnoreCase = True Then
        msOnlyCharacters = LCase$(msOnlyCharacters)
    End If
    For lPtr = 1 To Len(InputString)
        sChar = Mid$(InputString, lPtr, 1)
        If InStr(msOnlyCharacters, sChar) > 0 Then
            sResult = sResult & sChar
        End If
    Next lPtr
End If

If Len(msIgnoreCharacters) > 0 Then
    If mbIgnoreCase = True Then
        msIgnoreCharacters = LCase$(msIgnoreCharacters)
    End If
    For lPtr = 1 To Len(msIgnoreCharacters)
        sChar = Mid$(msIgnoreCharacters, lPtr, 1)
        sResult = Replace(sResult, sChar, "")
    Next lPtr
End If

AdjustStringForComparison = sResult

End Function

Private Function GetResultsWorksheet(ByVal WSName As String) As Worksheet
Dim lSheetsCount As Long
Dim sWSNumber As String

If Replace(LCase$(WSName), " ", "") = "<<no>>" Then
    Set GetResultsWorksheet = Nothing
Else
    On Error Resume Next
    Set GetResultsWorksheet = ThisWorkbook.Sheets(WSName)
    On Error GoTo 0
    If (GetResultsWorksheet Is Nothing) Then
        lSheetsCount = ThisWorkbook.Sheets.Count
        With ThisWorkbook
            lSheetsCount = .Sheets.Count
            Set GetResultsWorksheet = .Sheets.Add(after:=.Sheets(lSheetsCount))
        End With
        On Error Resume Next
        Err.Number = 0
        GetResultsWorksheet.Name = WSName
        If Err.Number > 0 Then
            MsgBox prompt:="Invalid sheet name '" & WSName & "'. Data being sent to sheet '" & _
                            GetResultsWorksheet.Name & "'", _
                    Buttons:=vbOKOnly + vbExclamation
        End If
    End If
    If Not (GetResultsWorksheet Is Nothing) Then
        On Error Resume Next
        With GetResultsWorksheet.CustomProperties
            .Item(1).Value = 0
            .Add Name:="LastRowUsed", Value:=0
        End With
    End If
End If
End Function

Private Function GetWorksheet(ByVal WSName As String, ByRef WB As Workbook) As Worksheet
Dim bThisWB As Boolean
Dim iWSPtr As Integer
Dim saWorksheet() As String
Dim WBInput As Workbook

Set GetWorksheet = Nothing
saWorksheet = Split(WSName, "!")
iWSPtr = UBound(saWorksheet)

Set WBInput = Nothing
On Error Resume Next
If iWSPtr = 0 Then
    Set WBInput = ThisWorkbook
    bThisWB = True
Else
    bThisWB = False
    Set WBInput = Workbooks.Open(Filename:=ThisWorkbook.Path & "\" & saWorksheet(0), ReadOnly:=True)
    Set WB = WBInput
End If
If WBInput Is Nothing Then
    MsgBox prompt:="Unable to open '" & saWorksheet(0), Buttons:=vbOKOnly + vbCritical
    Exit Function
End If

Set GetWorksheet = WBInput.Sheets(saWorksheet(iWSPtr))
If GetWorksheet Is Nothing Then
    If bThisWB = False Then WBInput.Close savechanges:=False
    MsgBox prompt:="Unable to access worksheet '" & WSName & "'", Buttons:=vbOKOnly + vbCritical
End If
End Function
Private Function PopulateDictionary(ByRef WS As Worksheet, _
                                    ByRef KeyColumns() As Integer, _
                                    ByVal HeadingRow As Long, _
                                    ByVal ReportSheet As Worksheet, _
                                    ByRef ColumnPositions() As Integer, _
                                    ByRef DupFormatRange As Range) As Object
Dim iPtr As Integer
Dim iKeyColsPtr As Integer
Dim iKeyPtr As Integer
Dim iCurCol As Integer
Dim iColEnd As Integer

Dim lRowEnd As Long
Dim lRow As Long
Dim lErrorCount As Long
Dim lReportPtr As Long

Dim rCur As Range

Dim sKey As String
Dim sCurKey As String
Dim sText As String
Dim saCurKey() As String

Dim vaItem() As Variant
Dim vaCurRow As Variant
Dim vaReport As Variant
Dim vReply As Variant

ReDim saCurKey(LBound(KeyColumns) To UBound(KeyColumns))

With WS.UsedRange
    iColEnd = .Column + .Columns.Count - 1
End With

Set PopulateDictionary = Nothing
Set PopulateDictionary = CreateObject("Scripting.Dictionary")
lRowEnd = WS.Cells(Rows.Count, ColumnPositions(0)).End(xlUp).Row
For lRow = HeadingRow + 1 To lRowEnd
    vaCurRow = WS.Range("A" & lRow).Resize(, iColEnd).Value
    sKey = ""
    For iKeyColsPtr = LBound(KeyColumns) To UBound(KeyColumns)
        iKeyPtr = KeyColumns(iKeyColsPtr)
        If iKeyPtr <> 0 Then
            saCurKey(iKeyColsPtr) = CStr(vaCurRow(1, iKeyPtr))
            sCurKey = LCase$(CStr(vaCurRow(1, iKeyPtr)))
            If mbFilterKey = True Then
                sCurKey = AdjustStringForComparison(sCurKey)
            End If
            sKey = sKey & "|" & sCurKey
        End If
    Next iKeyColsPtr
    If sKey = "" Then
        MsgBox prompt:="No key headings specified", _
                Buttons:=vbOKOnly + vbCritical, _
                Title:="PARAMETER ERROR"
        Set PopulateDictionary = Nothing
        Exit Function
    End If
    sKey = Mid$(sKey, 2)
    
    ReDim vaItem(1 To 1, 1 To UBound(ColumnPositions) + 2)
    For iPtr = 0 To UBound(ColumnPositions)
        iCurCol = ColumnPositions(iPtr)
        vaItem(1, iPtr + 1) = vaCurRow(1, iCurCol)
    Next iPtr
    vaItem(1, UBound(vaItem, 2)) = lRow         '-- Add row number to last element --
    
    If PopulateDictionary.Exists(sKey) Then
    
        If Not (ReportSheet Is Nothing) Then
            lDuplicateCount = lDuplicateCount + 1
            sText = "Duplicate key at row " & lRow & " of " & WS.Parent.Name & "!" & WS.Name & "."
            
            ReDim vaReport(1 To 1, 1 To UBound(vaItem, 2))
            vaReport(1, 1) = sText
            For lReportPtr = 1 To UBound(vaReport, 2) - 1
                vaReport(1, lReportPtr + 1) = vaItem(1, lReportPtr)
            Next lReportPtr

            mlReportRow = ReportSheet.CustomProperties.Item(1)
            mlReportRow = mlReportRow + 1
            ReportSheet.CustomProperties.Item(1).Value = mlReportRow
            
            DupFormatRange.Copy
            With ReportSheet.Range("A" & mlReportRow).Resize(, UBound(vaReport, 2))
                .Value = vaReport
                .PasteSpecial xlPasteFormats
'                .Characters.Font.Color = vbRed
            End With
        End If
        
    Else
        On Error Resume Next
        PopulateDictionary.Add Key:=sKey, Item:=vaItem
        If Err.Number <> 0 Then
            If MsgBox(prompt:="Error " & Err.Number & " in sheet " & WS.Name & " row " & lRow & vbCrLf & _
                                    Err.Description & vbCrLf & "Do you wish to ignore this and  continue?", _
                            Buttons:=vbYesNo + vbCritical, _
                            Title:="ERROR DETECTED") = vbNo Then
                Set PopulateDictionary = Nothing
                Exit Function
            End If
        End If
        On Error GoTo 0
    End If
Next lRow
End Function

Private Function GetParameters() As Boolean
Dim bError As Boolean
Dim iKeyFieldCount As Integer
Dim iPtr As Integer
Dim iParamCheck As Integer
Const iParamCompareSheets As Integer = 1
Const iParamResultsSheet As Integer = 2
Const iParamHeadings As Integer = 4

Dim lRow As Long
Dim sCurKey As String
Dim saCurInput() As String
Dim saHeadings() As String, saHeadingsA() As String
Dim vaParameters As Variant
Dim vaArrayResultsParams As Variant

Dim wsParams As Worksheet, wsTemp As Worksheet

On Error Resume Next
For iPtr = 1 To UBound(mwsaResultsSheets)
    Set mwsaResultsSheets(iPtr) = Nothing
Next iPtr
On Error GoTo 0

Set wsParams = Nothing
On Error Resume Next
Set wsParams = Sheets("Parameters")
On Error GoTo 0
If wsParams Is Nothing Then
    MsgBox prompt:="Cannot access 'Parameters' sheet", _
            Buttons:=vbOKOnly + vbCritical, _
            Title:="ERROR"
    GetParameters = False
    Exit Function
End If

lRow = wsParams.Cells(Rows.Count, "A").End(xlUp).Row
vaParameters = wsParams.Range("A1:B" & lRow).Value

ReDim msaHeadingRows(0 To 0)
msaHeadingRows(0) = "1"

mbDisplayOutputHeadings = True

iParamCheck = 0
For lRow = 2 To UBound(vaParameters, 1)
    sCurKey = NormaliseText(CStr(vaParameters(lRow, 1)))
    Select Case sCurKey
    Case "comparesheets"
        iParamCheck = iParamCheck Or iParamCompareSheets
        msaCompareSheets = Split(CStr(vaParameters(lRow, 2)), ",")
        If UBound(msaCompareSheets) <> 1 Then
            MsgBox prompt:="'Compare Sheets' parameter must have exactly two elements", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End If
        
        For iPtr = 0 To 1
            msaCompareSheets(iPtr) = Trim$(msaCompareSheets(iPtr))
            bError = msaCompareSheets(iPtr) = ""
            If bError = False Then
                saCurInput = Split(msaCompareSheets(iPtr), "!")
                bError = UBound(saCurInput) > 1
            End If
            If bError Then
                MsgBox prompt:="'Compare Sheets' parameter error", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
            End If
            
        Next iPtr
        
    Case "resultssheetduplicatekeydata1"
        Set mwsaResultsSheets(mlResultsPtrDupKey1) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        Set mrFormatDupKey1 = wsParams.Range("B" & lRow)
        
    Case "resultssheetduplicatekeydata2"
        Set mwsaResultsSheets(mlResultsPtrDupKey2) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        Set mrFormatDupKey2 = wsParams.Range("B" & lRow)
    
    Case "resultssheetmismatched"
        Set mwsaResultsSheets(mlResultsPtrMismatched) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        Set mrFormatMismatched = wsParams.Range("B" & lRow)
        
    Case "resultssheetmatched"
        Set mwsaResultsSheets(mlResultsPtrMatched) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        Set mrFormatMatched = wsParams.Range("B" & lRow)
    
    Case "resultssheetdata1only"
        Set mwsaResultsSheets(mlResultsPtrData1Only) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        Set mrFormatData1Only = wsParams.Range("B" & lRow)
    
    Case "resultssheetdata2only"
        Set mwsaResultsSheets(mlResultsPtrData2Only) = GetResultsWorksheet(WSName:=CStr(vaParameters(lRow, 2)))
        Set mrFormatData2Only = wsParams.Range("B" & lRow)
        
    Case "headingsrow"
        msaHeadingRows = Split(CStr(vaParameters(lRow, 2)), ",")
        
    Case "headings"
        iParamCheck = iParamCheck Or iParamHeadings
        saHeadings = Split(CStr(vaParameters(lRow, 2)), ",")
        
        ReDim msaHeadings1(0 To UBound(saHeadings))
        ReDim msaHeadings2(0 To UBound(saHeadings))
        ReDim miaHeadingCols1(0 To UBound(saHeadings))
        ReDim miaKeyFields1(0 To UBound(saHeadings))
        ReDim miaKeyFields2(0 To UBound(saHeadings))
        ReDim miaHeadingCols2(0 To UBound(saHeadings))
        ReDim mbaHeadingsInfo(0 To UBound(saHeadings))
        ReDim mbaKeyFields(0 To UBound(saHeadings))
        iKeyFieldCount = 0
        
        For iPtr = 0 To UBound(saHeadings)
            saHeadingsA = Split("=" & saHeadings(iPtr), "=")
            If UBound(saHeadingsA) < 1 Or UBound(saHeadingsA) > 2 Then
                MsgBox prompt:="Invalid headings value", Buttons:=vbOKOnly + vbCritical
                GetParameters = False
                Exit Function
            End If
            ReDim Preserve saHeadingsA(0 To 2)
            saHeadingsA(1) = Trim$(saHeadingsA(1))
            mbaHeadingsInfo(iPtr) = LCase$(Left$(saHeadingsA(1) & "123456", 6)) = "(info)"
            If mbaHeadingsInfo(iPtr) Then saHeadingsA(1) = Mid$(saHeadingsA(1), 7)
            mbaKeyFields(iPtr) = LCase$(Left$(saHeadingsA(1) & "12345", 5)) = "(key)"
            If mbaKeyFields(iPtr) Then
                iKeyFieldCount = iKeyFieldCount + 1
                saHeadingsA(1) = Mid$(saHeadingsA(1), 6)
            End If
            If saHeadingsA(2) = "" Then saHeadingsA(2) = saHeadingsA(1)
            msaHeadings1(iPtr) = saHeadingsA(1)
            msaHeadings2(iPtr) = Trim$(saHeadingsA(2))
        Next iPtr
        If iKeyFieldCount = 0 Then
            MsgBox prompt:="No key fields specified", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End If
        
    Case "displayoutputheadings"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbDisplayOutputHeadings = True
        Case "no"
            mbDisplayOutputHeadings = False
        Case Else
            MsgBox prompt:="'Display Output Headings' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
    
    Case "ignorecase"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbIgnoreCase = True
        Case "no"
            mbIgnoreCase = False
        Case Else
            MsgBox prompt:="'Ignore Case' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
                
    Case "filterkey"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbFilterKey = True
        Case "no"
            mbFilterKey = False
        Case Else
            MsgBox prompt:="'Filter Key' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
                       
    Case "ignorecharacters"
        msIgnoreCharacters = CStr(vaParameters(lRow, 2))
        
    Case "onlycharacters"
        msOnlyCharacters = CStr(vaParameters(lRow, 2))
    
    Case "showunchangedcells"
        Select Case LCase$(CStr(vaParameters(lRow, 2)))
        Case "yes"
            mbShowUnchangedCells = True
        Case "no"
            mbShowUnchangedCells = False
        Case Else
            MsgBox prompt:="'Show Unchanged Cells' parameter not 'Yes' or 'No'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End Select
        
    Case Else
        MsgBox prompt:="Unrecognised parameter in row " & lRow, Buttons:=vbOKOnly + vbCritical
        GetParameters = False
        Exit Function
    End Select
Next lRow

GetParameters = True

'vaArrayResultsParams = Array("Results Sheet Duplicate Key Data 1", _
'                             "Results Sheet Duplicate Key Data 2", _
'                             "Results Sheet Mismatched", _
'                             "Results Sheet Matched", _
'                             "Results Sheet Data 1 Only", _
'                             "Results Sheet Data 2 Only")
'
'For lRow = 1 To UBound(mwsaResultsSheets)
'    If (mwsaResultsSheets(lRow) Is Nothing) Then
'        MsgBox prompt:="Parameter " & vaArrayResultsParams(lRow - 1) & " has an invalid value"
'        GetParameters = False
'    End If
'Next lRow

End Function

Public Function StartsWith(str As String, prefix As String) As Boolean
    StartsWith = Left(str, Len(prefix)) = prefix
End Function

Private Function PopulateHeadingColumns(ByVal WS As Worksheet, _
                                        ByRef HeadingsTexts() As String, _
                                        ByRef HeadingsColumns() As Integer, _
                                        ByVal HeadingRow As Long, _
                                        ByRef KeyColumns() As Integer) As Boolean
Dim bFound As Boolean
Dim iPtrCol As Integer, iPtrHeading As Integer, iColEnd As Integer
Dim sCurHeading As String, sCur As String
Dim vaHeadings() As Variant

iColEnd = WS.Cells(HeadingRow, Columns.Count).End(xlToLeft).Column
vaHeadings = WS.Range("A" & HeadingRow & ":" & WS.Cells(HeadingRow, iColEnd).Address).Value

For iPtrHeading = LBound(HeadingsTexts) To UBound(HeadingsTexts)
    sCurHeading = NormaliseText(HeadingsTexts(iPtrHeading))
    bFound = False
    For iPtrCol = 1 To UBound(vaHeadings, 2)
        If sCurHeading = NormaliseText(CStr(vaHeadings(1, iPtrCol))) Then
            HeadingsColumns(iPtrHeading) = iPtrCol
            If mbaKeyFields(iPtrHeading) = True Then KeyColumns(iPtrHeading) = iPtrCol
            bFound = True
            Exit For
        End If
    Next iPtrCol
    If bFound = False Then
        MsgBox prompt:="Heading '" & HeadingsTexts(iPtrHeading) & "' not found in sheet '" & WS.Name, _
                Buttons:=vbOKOnly + vbCritical
                PopulateHeadingColumns = False
                Exit Function
    End If
Next iPtrHeading

PopulateHeadingColumns = True
End Function

Private Function NormaliseText(ByVal TextString As String) As String
'-- Convert to lower case and remove all but alphanumerics --
Dim iPtr As Integer
Dim sHold As String
Dim sChar As String
Dim sResult As String

sHold = Replace(LCase$(TextString), " ", "")
sResult = ""
For iPtr = 1 To Len(sHold)
    sChar = Mid$(sHold, iPtr, 1)
    If IsNumeric(sChar) Or sChar <> UCase$(sChar) Then
        sResult = sResult & sChar
    End If
Next iPtr
NormaliseText = sResult
End Function
 
Upvote 0
Hi,

On further testing, it seems to not work if you're not on Sheet1 when calling the macro.

Try this amended version:
Code:
Option Explicit
Dim miMaxColumns As Integer
Sub CompareSheets()
Dim bChanged As Boolean, baChanged() As Boolean
Dim iColEnd As Integer, iCol As Integer, iCol1 As Integer, iCol2 As Integer
Dim lRow1 As Long, lRow2 As Long, lReportRow As Long
Dim objDictOld As Object, objDictNew As Object
Dim vKeys As Variant, vKey As Variant
Dim vaInput() As Variant, vaOutput() As Variant, vaOutput2() As Variant
Dim vaInputOld As Variant, vaInputNew As Variant
Dim wsOld As Worksheet, wsNew As Worksheet, wsReport As Worksheet


Set wsOld = Sheets("Sheet1")
miMaxColumns = wsOld.Cells(1, Columns.Count).End(xlToLeft).Column
Set objDictOld = PopulateDictionary(WS:=wsOld)
Set wsNew = Sheets("Sheet2")
Set objDictNew = PopulateDictionary(WS:=wsNew)

Set wsReport = Sheets("Sheet3")

With wsReport
    .Cells.ClearFormats
    .Cells.ClearContents
End With

wsOld.Range("A1:" & wsOld.Cells(1, miMaxColumns).Address).Copy
wsReport.Range("B1").PasteSpecial xlPasteValues
Application.CutCopyMode = False

lReportRow = 1
vKeys = objDictOld.Keys
For Each vKey In vKeys
    ReDim vaInputOld(1 To 1, 1 To miMaxColumns)
    vaInputOld = objDictOld.Item(vKey)
    If objDictNew.exists(vKey) Then
        ReDim vaInputNew(1 To 1, 1 To miMaxColumns)
        vaInputNew = objDictNew.Item(vKey)
        ReDim vaOutput(1 To 1, 1 To miMaxColumns + 1)
        ReDim vaOutput2(1 To 1, 1 To miMaxColumns + 1)
        ReDim baChanged(1 To miMaxColumns)
        bChanged = False
        For iCol = 1 To miMaxColumns
            vaOutput(1, iCol + 1) = vaInputOld(1, iCol)
            If vaInputOld(1, iCol) <> vaInputNew(1, iCol) Then
                vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
                baChanged(iCol) = True
                bChanged = True
            End If
        Next iCol
        If bChanged Then
            lReportRow = lReportRow + 1
            For iCol = 1 To UBound(baChanged)
                If baChanged(iCol) Then
                    With wsReport
                        .Range(.Cells(lReportRow, iCol + 1).Address, _
                               .Cells(lReportRow + 1, iCol + 1).Address).Interior.Color = vbYellow
                    End With
                End If
            Next iCol
            
            vaOutput(1, 1) = "Changed"
            With wsReport
                .Range(.Cells(lReportRow, 1).Address, _
                       .Cells(lReportRow, miMaxColumns + 1).Address).Value = vaOutput
                lReportRow = lReportRow + 1
                .Range(.Cells(lReportRow, 1).Address, _
                       .Cells(lReportRow, miMaxColumns + 1).Address).Value = vaOutput2
            End With
        End If
        objDictOld.Remove vKey
        objDictNew.Remove vKey
    Else
        ReDim vaOutput(1 To 1, 1 To miMaxColumns + 1)
        vaOutput(1, 1) = "Deleted"
        For iCol = 1 To miMaxColumns
            vaOutput(1, iCol + 1) = vaInputOld(1, iCol)
        Next iCol
        
        lReportRow = lReportRow + 1
        With wsReport
            .Range(.Cells(lReportRow, 1).Address, .Cells(lReportRow, miMaxColumns + 1).Address).Value = vaOutput
            '-- Set the row to light grey
            .Range(.Cells(lReportRow, 2).Address, .Cells(lReportRow, miMaxColumns + 1).Address).Interior.ColorIndex = 15
        End With
    End If
Next vKey

If objDictNew.Count <> 0 Then
    vKeys = objDictNew.Keys
    For Each vKey In vKeys
        ReDim vaOutput2(1 To 1, 1 To miMaxColumns + 1)
        vaInputNew = objDictNew.Item(vKey)
        vaOutput2(1, 1) = "Inserted"
        For iCol = 1 To miMaxColumns
            vaOutput2(1, iCol + 1) = vaInputNew(1, iCol)
        Next iCol
        lReportRow = lReportRow + 1
        With wsReport
            .Range(.Cells(lReportRow, 1).Address, .Cells(lReportRow, miMaxColumns + 1).Address).Value = vaOutput2
            '-- Set the row to light green
            .Range(.Cells(lReportRow, 2).Address, .Cells(lReportRow, miMaxColumns + 1).Address).Interior.ColorIndex = 4
        End With
    Next vKey
End If

objDictOld.RemoveAll
Set objDictOld = Nothing
objDictNew.RemoveAll
Set objDictNew = Nothing
End Sub
Private Function PopulateDictionary(ByRef WS As Worksheet) As Object
Dim lRowEnd As Long, lRow As Long
Dim rCur As Range
Dim sKey As String

Set PopulateDictionary = Nothing
Set PopulateDictionary = CreateObject("Scripting.Dictionary")
lRowEnd = WS.Cells(Rows.Count, "A").End(xlUp).Row
For lRow = 2 To lRowEnd
    sKey = Trim$(LCase$(CStr(WS.Range("A" & lRow).Value)))
    On Error Resume Next
    PopulateDictionary.Add key:=sKey, Item:=WS.Range(WS.Cells(lRow, 1).Address, _
                                            WS.Cells(lRow, miMaxColumns).Address).Value
    On Error GoTo 0
Next lRow
End Function



The code is a life saver,but I need to ask one thing, If my value in sheet 1,2 A4 and B4 are same, it won't compare, how can I make it to compare because maybe A4 and B4 are similar but in the further cols like D4/E4 are different in both sheets. pleasehelp
 
Upvote 0
Hi sainathd, not sure what you mean.
Can you post an example of what parameters you used plus the contents of the two sheets please?
 
Upvote 0
Hi sainathd, not sure what you mean.
Can you post an example of what parameters you used plus the contents of the two sheets please?


sheet 1:

Part / InterfaceFunctionRequirement / Specification
(reference)
(Loss of Function) FailureDue to Failure
A1xxxx
A2axxx
BB3xxyx
D4xxyx
C5xxxx

<tbody>
</tbody>

sheet 2:

Part / InterfaceFunctionRequirement / Specification
(reference)
(Loss of Function) FailureDue to Failure
A1xxxx
A2xxxx
BB3xxxx
D4xxxx
C5xxxx

<tbody>
</tbody>


If you observe in sheet 1, under part/interface,there are A two times, when the same is compared with sheet 2, even that has two A's but the data is different in 2nd row(2nd A) i.e., requirement/specification is different in row 2 which is not detected/compared.Let me put the result after running the macro,


Part / InterfaceFunctionRequirement / Specification
(reference)
(Loss of Function) FailureDue to Failure
ChangedBB 3xxyx
x
ChangedD 4xxyx
x

<tbody>
</tbody>
Here since the first "A" is same it is not mentioned which is fine, but the second "A" has the different data different which is not detected but that needs to be detected, did you understand?
 
Last edited:
Upvote 0

Forum statistics

Threads
1,221,528
Messages
6,160,346
Members
451,638
Latest member
MyFlower

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top