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 weizhang,

Looking at your example sheets, can you confirm:
1) If we concatenated "CityTown" and "Sequence" from Sheet1 and used that as a key, would we get a match with "Liability", "CoverageType" and "Sequence" from sheet2?
2) The 3 fields we need to report on are changes to "Display Value", "Data Value" and "Status" - in your example, "Display Value" and "Data Value" fields for the two sheets seem to be completely different.
 
Upvote 0

Excel Facts

Whats the difference between CONCAT and CONCATENATE?
The newer CONCAT function can reference a range of cells. =CONCATENATE(A1,A2,A3,A4,A5) becomes =CONCAT(A1:A5)
Hi weizhang,

Looking at your example sheets, can you confirm:
1) If we concatenated "CityTown" and "Sequence" from Sheet1 and used that as a key, would we get a match with "Liability", "CoverageType" and "Sequence" from sheet2?
2) The 3 fields we need to report on are changes to "Display Value", "Data Value" and "Status" - in your example, "Display Value" and "Data Value" fields for the two sheets seem to be completely different.

Thanks so much for you quick reply. I think I did not make my self clear.
The two tables in the example are in the same sheet, let's say sheet1. Sheet2 will have the similar tables. But tables might have different entries. In this case, I need to report what is the different between each tables in the each sheet.

I looked at the all the tables. And I found out if we concatenate column A,B,C, it will work as a unique key for all the tables.
So, I updated the code for sKey,
sKey = Trim$(LCase$(CStr(WS.Cells(lRow, 1).Value))) & Trim$(LCase$(CStr(WS.Cells(lRow, 2).Value))) & Trim$(LCase$(CStr(WS.Cells(lRow, 3).Value)))

It worked fine. But it needs some modification. Because it displayed all the differences under the first table's attributes.

CityTownSequenceDisplay ValueDataValueStatus
ChangedDeductible15050Added
Deleted
ChangedLiabilityLimitTextCombined Single Limit365,00065,000Added
15,00015,000Deleted
ChangedLiabilityLimitTextCombined Single Limit4100,000100,000Added
20,00020,000Deleted
InsertedCityTown72WeiserWeiserAdded
InsertedCityTown73WendellWendellAdded
InsertedDeductible83,0003,000Added
InsertedDeductible95,0005,000Added

<tbody>
</tbody>


So the question is how to make the code better to report the differences for different tables. Hopefully, I made myself clear this time.
Thanks again for your help.
______________________________________________

Updated: Looks like I have to run it twice to get the differences. The first run will only populate the headings of the table. Do you know why?
 
Last edited:
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

For anyone who can help, this macro seems to do EXACTLY what I want but one problem...it doesn't. It will post the results in Sheet3 with the information from Row1. Nothing else will happen. Any thoughts?
 
Upvote 0
Hi, Can you post a sample of your two input sheets?

Sure. I am trying to Compare Sheet 1 with Sheet 2. Sheet 1 is the the old information and Sheet 2 is the new. I would like to so what changes happened in Sheet 2 depending what changed, added, or deleted.

I am trying to use a full row as the value to search an entire sheet. If the row is found, nothing happens. If not found, some kind of result will be recorded on the results page.

Sheet1
VsDV7RK.png

Sheet2
X8rT7oz.png

Sheet 3
8Nwwo5y.png
 
Upvote 0
Hi,

The macro currently assumes that column A is a unique key, which, from the above sample, it clearly isnt. Can I assume that we can construct a unique key using columns A,B and C?
 
Upvote 0
Hi,

The macro requires a unique key so that it can identify rows which have changed as opposed to new and deleted rows.
Currently it assumes that column A contains that unique key, but can be amended to include other columns if required.
 
Upvote 0
Ok, try this macro, it's controlled by a Parameters sheet, but it still needs a record key (or "fingerprint").

Example of Parameters sheet (which MUST be named "Parameters":

Excel 2003
ABC
1KeywordValueComment
2Compare Sheetsold, NewSheets to be compared
3Results SheetOutputSheet to contain comparison results
4Headings(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: > an optional Heading descriptor which is one of "(key)" or "(info)" "(key)" indicates that the fields under the heading definition is part of the record key. At least one "(key)" heading must be present. "(info)" indicates that the field is to be displayed, but is not to be part of the comparison. > The heading from the first sheet to be compared / displayed. > 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


Example of First Comparison sheet:

Excel 2003
ABCDEFG
1ForenameSurnameAddress Line 1Extra Field APost TownPost CodeComment
2Forename2Surname2Address 2Extra Field A 2Post Town2Post Code2Comment2
3Forename3Surname3Address 3Extra Field A 3Post Town3Post Code3Comment3
4Forename4Surname4$C$4Extra Field A 4Post Town4Post Code4Comment4
5Forename5Surname5Address 5Extra Field A 5$E$5Post Code5Comment5
6Forename6Surname6Address 6Extra Field A 6Post Town6$F$6Comment6
7Forename8Surname8Address 8Extra Field A 7Post Town8Post Code8Comment8
8Forename9Surname9Address 9Extra Field A 8Post Town9Post Code9$G$8
9Forename10Surname10Address 10Extra Field A 9Post Town10Post Code10Comment10
10Forename11Surname11Address 11Extra Field A 10Post Town11Post Code11Comment11
11Forename12Surname12Address 12Extra Field A 11Post Town12Post Code12Comment12
12Forename13Surname13Address 13Extra Field A 12Post Town13Post Code13$G$12
13Forename14Surname14Address 14Extra Field A 13Post Town14Post Code14Comment14
14Forename15Surname15Address 15Extra Field A 14Post Town15Post Code15Comment15
15Forename16Surname16Address 16Extra Field A 15Post Town16Post Code16Comment16
16Forename17Surname17Address 17Extra Field A 16Post Town17Post Code xxx$G$16
17Forename18Surname18Address 18Extra Field A 17Post Town18Post Code18Comment18
18Forename19Surname19Address 19Extra Field A 18Post Town19Post Code19Comment19
19Forename20Surname20Address 20Extra Field A 19Post Town20Post Code20Comment20
Old


Example of sheet to be compared against:

Excel 2003
ABCDEFG
1Last NameFirst NameAddressPost TownExtra Field BPost CodeComment
2Surname2Forename2Address 2Post Town2Extra Field B 2Post Code2Comment2
3Surname3Forename3Address 3Post Town3Extra Field B 3Post Code3Comment3
4Surname4Forename4Address 4Post Town4Extra Field B 4Post Code4Comment4
5Surname5Forename5Address 5Post Town5Extra Field B 5Post Code5Comment5
6Surname6Forename6Address 6Post Town6Extra Field B 6Post Code6Comment6
7Surname7Forename7Address 7Post Town7Extra Field B 7Post Code7Comment7
8Surname8Forename8Address 8Post Town8Extra Field B 8Post Code8Comment8
9Surname9Forename9Address 9Post Town9Extra Field B 9Post Code9Comment9
10Surname10Forename10Address 10Post Town10Extra Field B 10Post Code10Comment10
11Surname11Forename11Address 11Post Town11Extra Field B 11Post Code11Comment11
12Surname12Forename12Address 12Post Town12Extra Field B 12Post Code12Comment12
13Surname13Forename13Address 13Post Town13Extra Field B 13Post Code13Comment13
14$A$14Forename14Address 14Post Town14Extra Field B 14Post Code14Comment14
15Surname15Forename15Address 15Post Town15Extra Field B 15Post Code15Comment15
16Surname16Forename16Address 16Post Town16Extra Field B 16Post Code16Comment16
17Surname17Forename17Address 17Post Town17Extra Field B 17Post Code17Comment17
18Surname18Forename18Address 18Post Town18Extra Field B 18Post Code18Comment18
19Surname19Forename19Address 19Post Town19Extra Field B 19Post Code19Comment19
20Surname20Forename20Address 20Post Town20Extra Field B 20Post Code20Comment20
New


continued ....
 
Upvote 0
... continued

Example of Results:

Excel 2003
ABCDEFG
1Forename / First NameSurname / Last NameAddress Line 1 / AddressPost TownPost CodeComment
2ChangedForename4Surname4$C$4Post Town4Post Code4Comment4
3Address 4
4ChangedForename5Surname5Address 5$E$5Post Code5Comment5
5Post Town5
6ChangedForename6Surname6Address 6Post Town6$F$6Comment6
7Post Code6
8Old onlyForename14Surname14Address 14Post Town14Post Code14Comment14
9ChangedForename17Surname17Address 17Post Town17Post Code xxx$G$16
10Post Code17Comment17
11New onlyForename7Surname7Address 7Post Town7Post Code7Comment7
12New onlyForename14$A$14Address 14Post Town14Post Code14Comment14
Output


And the code:
Code:
Option Explicit

Dim mbaKeyFields() As Boolean
Dim mbaKeyCols1() As Boolean
Dim mbaKeyCols2() As Boolean
Dim mbaHeadingsInfo() As Boolean

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

Dim msaCompareSheets() As String
Dim msResultsSheet As String
Dim msaHeadings1() As String
Dim msaHeadings2() As String

Dim mwsInputs() 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 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 vaHeadings() As Variant
Dim wsOld As Worksheet, wsNew As Worksheet, wsReport As Worksheet

If GetParameters = False Then Exit Sub

Set wsOld = mwsInputs(0)
Set wsNew = mwsInputs(1)

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

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

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

Set objDictNew = PopulateDictionary(WS:=wsNew, _
                                    KeyColumns:=miaKeyFields2, _
                                    ColumnPositions:=miaHeadingCols2)
If objDictNew Is Nothing Then
    Exit Sub
End If

Set wsReport = Sheets(msResultsSheet)

With wsReport.UsedRange
    .ClearFormats
    .ClearContents
End With

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
wsReport.Range("A1", wsReport.Cells(1, UBound(vaHeadings, 2)).Address).Value = vaHeadings

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)
                If mbaHeadingsInfo(iCol - 1) = False Then
                    baChanged(iCol) = True
                    bChanged = True
                End If
            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) = wsOld.Name & " only"
        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) = wsNew.Name & " only"
        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, _
                                    ByRef KeyColumns() As Integer, _
                                    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 lDuplicateCount As Long
Dim rCur As Range
Dim sKey As String
Dim sMessage As String
Dim vaItem() As Variant
Dim vaCurRow As Variant
Dim vReply As Variant

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 = 2 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
            sKey = sKey & "|" & LCase$(CStr(vaCurRow(1, iKeyPtr)))
        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) + 1)
    For iPtr = 0 To UBound(ColumnPositions)
        iCurCol = ColumnPositions(iPtr)
        vaItem(1, iPtr + 1) = vaCurRow(1, iCurCol)
    Next iPtr
    If PopulateDictionary.Exists(sKey) Then
        lDuplicateCount = lDuplicateCount + 1
        If lDuplicateCount < 11 Then
            sMessage = sMessage & "Duplicate key in sheet " & WS.Name & " row " & lRow & vbCrLf
        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
If lDuplicateCount > 10 Then
    sMessage = sMessage & "(Only first 10 duplicate keys displayed)"
End If
If lDuplicateCount > 0 Then
    If MsgBox(prompt:=lDuplicateCount & " duplicate keys were ignored:" & vbCrLf & sMessage & _
                      "Do you wish to continue?", _
              Buttons:=vbYesNo + vbCritical, _
              Title:="DUPLICATE KEY(S) DETECTED") = vbNo Then
        Set PopulateDictionary = Nothing
        Exit Function
    End If
End If
End Function

Private Function GetParameters() 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 saHeadings() As String, saHeadingsA() As String
Dim vaParameters As Variant
Dim wsParams As Worksheet, wsTemp As Worksheet

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

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 has too many elements", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End If
        ReDim mwsInputs(0 To 1)
        For iPtr = 0 To 1
            Set mwsInputs(iPtr) = Nothing
            On Error Resume Next
            Set mwsInputs(iPtr) = Sheets(Trim$(msaCompareSheets(iPtr)))
            On Error GoTo 0
            If mwsInputs(iPtr) Is Nothing Then
                MsgBox prompt:="Cannot access compare sheet '" & msaCompareSheets(iPtr) & "'", Buttons:=vbOKOnly + vbCritical
                GetParameters = False
                Exit Function
            End If
        Next iPtr
        
    Case "resultssheet"
        iParamCheck = iParamCheck Or iParamResultsSheet
        msResultsSheet = CStr(vaParameters(lRow, 2))
        Set wsTemp = Nothing
        On Error Resume Next
        Set wsTemp = Sheets(msResultsSheet)
        On Error GoTo 0
        If wsTemp Is Nothing Then
            MsgBox prompt:="Cannot access Results sheet '" & msResultsSheet & "'", Buttons:=vbOKOnly + vbCritical
            GetParameters = False
            Exit Function
        End If
        
    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 Else
        MsgBox prompt:="Unrecognised parameter in row " & lRow, Buttons:=vbOKOnly + vbCritical
        GetParameters = False
        Exit Function
    End Select
Next lRow

If iParamCheck <> 7 Then
    MsgBox prompt:="Missing parameter(s)!", Buttons:=vbOKOnly + vbCritical
    GetParameters = False
    Exit Function
End If

GetParameters = True
End Function

Private Function PopulateHeadingColumns(ByVal WS As Worksheet, _
                                        ByRef HeadingsTexts() As String, _
                                        ByRef HeadingsColumns() As Integer, _
                                        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(1, Columns.Count).End(xlToLeft).Column
vaHeadings = WS.Range("A1:" & WS.Cells(1, 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

Forum statistics

Threads
1,216,099
Messages
6,128,816
Members
449,469
Latest member
Kingwi11y

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