3

I've a question similar to the one on the post VBA macro to compare two columns and color highlight cell differences.

I used it as reference point, but now I'm stuck for hours solving my case. Code included below, I'll explain my case first for better understanding and to be easier to follow.

Case: I've the following Worksheet before any manipulation. I'm comparing the columns "A:B" and "D:E", etc (from row 3 until the last used row). See the screenshot below for a better visualization (this is just part of the data).

Worksheet Before

Now I would like to see 2 actions performed:

  1. Highlight the cells in A column and D column that are not part of the B and E column - I'll refer to these cells as errors
  2. Copy the value of the errors (highlighted cell (from A and D)) into the C and F column (this is the "Review column" - which is always 2 columns to the right in relation to the initial column)

See the screenshot below for a better visualization

Worksheet After1

CODE:

Sub compare_cols()

    Dim Report As Worksheet
    Dim i As Integer, j As Integer
    Dim lastRow As Integer

    Set Report = Excel.Worksheets("Check_Sheet")

    lastRow = 80

    arrInputCheckSheet= Array("A", "D", "G", "J", "M", "P", "S", "V", "Y") 'I will use these columns to compare against the next array
    arrMDCheckSheet = Array("B", "E", "H", "K", "N", "Q", "T", "W", "Z") 'I will use these columns as reference 


    Application.ScreenUpdating = False

    For a = LBound(arrInputCheckSheet) To UBound(arrInputCheckSheet)
        For i = 3 To lastRow
            For j = 3 To lastRow
                If Report.Cells(i, arrInputCheckSheet(a)).Value <> "" Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.
                    If InStr(1, Report.Cells(j, arrMDCheckSheet(a)).Value, Report.Cells(i, arrInputCheckSheet(a)).Value, vbTextCompare) > 0 Then 
                        Report.Cells(i, arrInputCheckSheet(a)).Interior.Color = RGB(156, 0, 6) 'Dark red background
                        Report.Cells(i, arrInputCheckSheet(a)).Font.Color = RGB(255, 199, 206) 'Light red font color
                        Exit For
                    Else
                  End If
                End If
            Next j
        Next i
    Next a

Application.ScreenUpdating = True

End Sub

Problem:

  1. I'm trying to highlight the error cells with dark red background. but this code is doing exactly the opposite (is highlighting the values that match).
  2. How can I make the error value (the one that gets highlighted) appear in the the "check column".

I really appreciate any suggestion and support you can give to me

Thank you very much and have a nice day

  • 2
    Did you try to change the `> 0` into a `= 0` in `InStr(1, Report.Cells(j, arrMDCheckSheet(a)).Value, Report.Cells(i, arrInputCheckSheet(a)).Value, vbTextCompare) = 0`? This makes it the opposite which should be the the values that don't match. • Also don't use `Integer` for row counting. Excel has more rows than Integer can handle. It is recommended [always to use Long instead of Integer](https://stackoverflow.com/a/26409520/3219613) in VBA since there is no benefit in using `Integer` at all. – Pᴇʜ May 29 '18 at 11:00
  • @Pᴇʜ thank you for your suggestion, the thing is that when I change the >0 to =0 it just highlights all the cells (literally). – Martim On Fire May 29 '18 at 12:31
  • did you remove the color from all cells before you tested it? otherwise it fails. – Pᴇʜ May 29 '18 at 12:33
  • Yes I remove all the formatting beforehand. it's annoying. I tried to do also the opposite statement. Do nothing if `>0` and, else highlight. do you have any idea? btw, thank you for the Long instead of Integer. – Martim On Fire May 29 '18 at 12:40
  • @Pᴇʜ besides this color issue, can you advise me on how to take the error value (the one that gets highlighted) and copy it into the 2 columns away to its actual position? Much appreciated – Martim On Fire May 29 '18 at 12:45
  • 1
    I suggest to use the [WorksheetFunction.Match Method](https://msdn.microsoft.com/en-us/vba/excel-vba/articles/worksheetfunction-match-method-excel) instead of that second `j` loop. And use the [Range.Offset Property](https://msdn.microsoft.com/en-us/vba/excel-vba/articles/range-offset-property-excel) to address the offset cell to copy the value. – Pᴇʜ May 29 '18 at 12:48
  • @Pᴇʜ thanks for your suggestion. Do you think that I can just use the copy (into 2 columns to it self) right after the cell is highlighted? I mean, the cells are being highlighted, should be easy to copy that cells somewhere else without much hassle. Thank you in advance for the tips and support – Martim On Fire May 29 '18 at 13:05
  • 1
    Yes you can use the `.Offset` see my answer for a full example – Pᴇʜ May 29 '18 at 13:15

3 Answers3

2

I suggest to use the WorksheetFunction.Match Method instead of that second j loop. And use the Range.Offset Property to address the offset cell to copy the value.

Here is an example for the data shown in your screenshot.

Option Explicit

Sub compare_cols()
    Dim Report As Worksheet
    Set Report = Excel.Worksheets("Check_Sheet")

    Dim lastRow As Long
    lastRow = 10

    Dim arrInputCheckSheet As Variant
    arrInputCheckSheet = Array("A", "D") 'I will use these columns to compare against the next array

    Dim arrMDCheckSheet As Variant
    arrMDCheckSheet = Array("B", "E") 'I will use these columns as reference

    Dim j As Long
    j = 13 'start at row 13

    'Application.ScreenUpdating = False 'disable this during debug
    Const firstRow As Long = 3
    Dim a As Long
    For a = LBound(arrInputCheckSheet) To UBound(arrInputCheckSheet)
        Dim i As Long
        For i = firstRow To lastRow
            Dim MatchRow As Long
            If Report.Cells(i, arrInputCheckSheet(a)).Value <> vbNullString Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.

                On Error Resume Next 'match throws an error if nothing matched
                MatchRow = 0
                MatchRow = Application.WorksheetFunction.Match(Report.Cells(i, arrInputCheckSheet(a)).Value, Report.Range(Cells(firstRow, arrMDCheckSheet(a)), Cells(lastRow, arrMDCheckSheet(a))), 0)
                On Error GoTo 0 're-activate error reporting

                If MatchRow = 0 Then
                    'no match
                    With Report.Cells(i, arrInputCheckSheet(a))
                        .Interior.Color = RGB(156, 0, 6) 'Dark red background
                        .Font.Color = RGB(255, 199, 206) 'Light red font color

                        .Offset(0, 2).Value = .Value 'copy value

                        'copy to different sheet
                        Sheets("Check_Sheet").Cells(j, arrControlSheet(a)) = .Value
                        j = j + 1 'increase row counter after each copy
                    End With
                End If
            End If

        Next i
    Next a

    'Application.ScreenUpdating = True
End Sub
Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
  • I really appreciate the support, the code worked perfectly, thank you a ton :) Cheers – Martim On Fire May 29 '18 at 14:07
  • I've an extra small question. How can I take advantage of the `.Offset(0, 2).Value = .Value` code to ALSO paste the same cell value to another sheet? I tried the following, but no success `.Sheets("Check_Sheet").Cells(13, arrControlSheet(a)) = .Value` Can you advise me? Much appreciated – Martim On Fire May 30 '18 at 12:50
  • 1
    should work without the first dot `Sheets("Check_Sheet").Cells(13, arrControlSheet(a)) = .Value` (right before `End With`). If it doesn't work please describe what exactly goes wrong or what error you get. – Pᴇʜ May 30 '18 at 12:54
  • it worked perfectly (I will search more about what's the role of the dot). Thanks a ton :) :) – Martim On Fire May 30 '18 at 15:29
  • 1
    @MartimOnFire A dot in the beginning is just a shortcut to the `With` statement. It means that what is behind the with is used before the line starting with the dot. So in this case `.Value` is actually `Report.Cells(i, arrInputCheckSheet(a)).Value` because of the `With Report.Cells(i, arrInputCheckSheet(a))` – Pᴇʜ May 30 '18 at 15:37
  • I noticed that it was going all errors to the cell 13, I tried to make it variable (cell13 +1) but I couldn't make it work, can you please let me know how can I do it? – Martim On Fire May 30 '18 at 15:41
  • @MartimOnFire you will need an additional counter for that. Eg start with `j = 13` and use `j = j + 1` to increase it after you copied a value – Pᴇʜ May 30 '18 at 15:43
  • Exactly, I did that, but probably I'm not inserting in the right place then, as it keeps replacing the 13 row. as the conditions are so many (if + if + with) I lost the plot of it.. – Martim On Fire May 30 '18 at 15:51
  • hey there, a quick question on this code. this is a bit strange, but when I run (pressing F5) the code thru the VBA editor (within Excel) is different from what when I run the code thru a Button (and the same macro assigned to that same button). Is very strange. When I try in a different Computer the output is the same as when I run thru VBA editor, and that output is not good. – Martim On Fire Jun 05 '18 at 17:43
  • When I run thru the VBA editor, the output looks like is just copy/paste all the array "arrInputCheckSheet" into the 2 columns at its right side (the offset) and is coloring all with red (instead of the non-matching ones) – Martim On Fire Jun 05 '18 at 17:47
  • I found that if I run the code with the code break mode (line by line), it gives me the output that I want - just identify the non-matching and copies into the other 2 columns (one offset and other in other workbook). Any idea? – Martim On Fire Jun 06 '18 at 06:56
  • After a couple of hours looking into the code over and over again, I figured out that adding a dot before `cells` in the following code `MatchRow = Application.WorksheetFunction.Match(Report.Cells(i, arrInputCheckSheet(a)).Value, Report.Range(Cells(firstRow, arrMDCheckSheet(a)), Cells(lastRow, arrMDCheckSheet(a))), 0)` made it work as I wanted. the code is now: `MatchRow = Application.WorksheetFunction.Match(Report.Cells(i, arrInputCheckSheet(a)).Value, Report.Range(.Cells(firstRow, arrMDCheckSheet(a)), .Cells(lastRow, arrMDCheckSheet(a))), 0)` Thank you once again for the huge help – Martim On Fire Jun 06 '18 at 12:14
1

As mentioned in the comments, you need to check if the InStr function returns zero (see the MSDN page on InStr), not greater than zero. Note that using InStr will match partial matches too (if you have "a" in column A, that will match with any string in column B that contains "a"). If you want more exact matches use = or the Like keyword (in conjunction with something like the UCASE function to match across cases). However, the reason that alone will not work is that it does this if the A column cell is not equal to ALL of the column B cells. It checks against the first one, if it isn't equal to that it gets highlight, and on to the second entry in row A to repeat. You need an If-Else to do something if it does match, and you will need to check every entry (the exit for statement needs to go in the case where there IS a match). To copy your highlighted cells into column C, F, etc... you can offset two columns from your current A column when inside the inner If statement.

If UCase(Report.Cells(j, arrMDCheckSheet(a)).Value) Like UCase(Report.Cells(i, arrInputCheckSheet(a)).Value) Then
    Report.Cells(i, arrInputCheckSheet(a)).ClearFormatting
    Exit For
Else
    Report.Cells(i, arrInputCheckSheet(a)).Interior.Color = RGB(156, 0, 6) 'Dark red background
    Report.Cells(i, arrInputCheckSheet(a)).Font.Color = RGB(255, 199, 206) 'Light red font color
    Report.Cells(i, arrInputCheckSheet(a)).Offset(0,2).Value = Report.Cells(i, arrInputCheckSheet(a)).Value  ' This copies to the 3rd column
End If

Or using InStr:

If InStr(1,Report.Cells(j, arrMDCheckSheet(a)).Value,Report.Cells(i, arrInputCheckSheet(a)).Value) = 0 Then

It would also be quicker to use a while statement instead of the for loops to keep going until a blank cell is encountered so that you don't keep checking blank cells.

i = 3
Do While Report.Cells(i, arrInputCheckSheet(a)).Value <> ""
    j = 3
    Do While Report.Cells(j, arrMDCheckSheet(a)).Value <> ""
        ' this would be the if statements, use exit do instead of exit for
        j = j + 1
    Loop
    i = i + 1
Loop
user2731076
  • 689
  • 1
  • 6
  • 21
0

Another possibility; make a string of your arrMDCheckSheet-array (I changed the Instr function and added one line for third column, to keep your original code as much as it is)

    For a = LBound(arrInputCheckSheet) To UBound(arrInputCheckSheet)
    For i = 3 To lastRow
        For j = 3 To lastRow
            If Report.Cells(i, arrInputCheckSheet(a)).Value <> "" Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.
                If InStr(1, Join(Application.Transpose(Report.Range(Cells(3, arrMDCheckSheet(a)), Cells(lastRow, arrMDCheckSheet(a)))), "|"), Report.Cells(i, arrInputCheckSheet(a)).Value, vbTextCompare) = 0 Then
                    Report.Cells(i, arrInputCheckSheet(a)).Interior.Color = RGB(156, 0, 6) 'Dark red background
                    Report.Cells(i, arrInputCheckSheet(a)).Font.Color = RGB(255, 199, 206) 'Light red font color
                    Report.Cells(i, arrInputCheckSheet(a)).Offset(, 2) = Report.Cells(i, arrInputCheckSheet(a)) 'added
                    Exit For
                Else
              End If
            End If
        Next j
    Next i
Next a
EvR
  • 3,418
  • 2
  • 13
  • 23