0

I am having a problem with a small part of a macro. Essentially, what it is supposed to do is check if a value is found whithin a column, if it isn't then it continues through the rest of the code:

Dim p As Long
Dim j As Long
Dim LastRow2 As Long
Dim LastRow3 As Long
Dim FindRow As Range
Dim a As Worksheet
Dim b As Worksheet
Dim c As Worksheet
Dim d As Worksheet

Set a = Workbooks("Item ID Comparison").Worksheets(1)
Set b = Workbooks("Item ID Comparison").Worksheets(2)
Set c = Workbooks("Item ID Comparison").Worksheets(3)
Set d = Workbooks("Prueba").Worksheets(1)
LastRow3 = d.Cells(Rows.Count, "F").End(xlUp).Row

For p = 2 to LastRow3

    If b.Columns(6).Find(d.Cells(p, 6), lookat:=xlWhole) Is Nothing Then
        Set FindRow = d.Columns(6).Find(a.Cells(p, 7))
        j = FindRow.Row
        If Not a.Columns(7).Find(d.Cells(p, 6), lookat:=xlWhole) Is Nothing Then
            LastRow2 = c.Cells(Rows.Count, "A").End(xlUp).Row
            c.Range("A" & LastRow2 + 1).Value = d.Cells(j, "A").Value
            c.Range("B" & LastRow2 + 1).Value = d.Cells(j, "B").Value
            c.Range("C" & LastRow2 + 1).Value = d.Cells(j, "C").Value
            c.Range("D" & LastRow2 + 1).Value = d.Cells(j, "D").Value
            c.Range("E" & LastRow2 + 1).Value = d.Cells(j, "E").Value
            c.Range("F" & LastRow2 + 1).Value = d.Cells(j, "F").Value
            c.Range("G" & LastRow2 + 1).Value = d.Cells(j, "G").Value
            c.Range("H" & LastRow2 + 1).Value = d.Cells(j, "H").Value
            c.Range("I" & LastRow2 + 1).Value = d.Cells(j, "I").Value
        End If
    End If
Next p

The problem is that excel seems to be ignoring the IF statement for some reason (while actually following the second IF statement. I end up finding values within the data-set that were found within b.columns(6).

Any help would be greatly appreciated.

Paul
  • 4,160
  • 3
  • 30
  • 56
PaulRey
  • 13
  • 5
  • 4
    What is `p` set to when the `If` executes? I don't see it being assigned at all, but you have `Next p` at the end which makes me think there should be a `For` loop that has no header. – MoondogsMaDawg Jan 05 '18 at 15:11
  • Then there is a problem with the data. with `lookat:=xlWhole` the code is expecting a match to the whole cell. If you your cell has a space or other unprintable character then it will not match. Look at your data and make sure they match, and do not have other characters seen or unseen. – Scott Craner Jan 05 '18 at 15:21
  • @MoondogsMaDawg My bad, I forgot to add the start of the loop. I Have edited the code to show correctly. – PaulRey Jan 05 '18 at 15:21
  • @ScottCraner I have already checked the data. I even created a data-set manually to double check, and the same problem happens. – PaulRey Jan 05 '18 at 15:23
  • 3
    You are going need to do the debugging on this one, we can only guess. Debug and go line by line making sure that you are pointed at the correct sheets, that the value you are testing is the one you expect to be testing and the range in which you are looking is the one you expect. We can only guess. – Scott Craner Jan 05 '18 at 15:25
  • Just want to ask, you haven't got `On Error Resume Next` set, right? – Brownish Monster Jan 05 '18 at 15:26
  • 1
    I have to say: well done for using `Next p` - the number of people who don't... – Paul Jan 05 '18 at 15:32
  • Are you certain that, with the code as it is above, it ignores the first if but runs the second? What is far more likely is that the first `If` is always evaluating to `True` since there shouldn't be any way that an inner conditional processes without the outer conditional first being satisfied. – Brandon Barney Jan 05 '18 at 16:37
  • 2
    @Paul In my experience I have seen more cases where needing a `Next Foo` to be a code smell versus a sign of clean code. Generally speaking, if a `Next Foo` and `Next Bar` and so on are needed, additional abstraction is needed. – Brandon Barney Jan 05 '18 at 16:39
  • I think the first if statement should say `> 0` – Brownish Monster Jan 06 '18 at 13:13
  • @BrandonBarney: Code abstraction and Excel VBA shouldn't really be mentioned in the same sentence. I don't think I've ever met an Excel coder who writes polymorphic code in VBA. I have met quite a few users who employ multiple `For Next` loops in conjunction with `GoTo` - a dangerous combination. Try jumping out of an inner loop to an outer loop with unqualified `Next` statements - it's not pretty. You tend to find less flowers in VBA, it's more 'functional' code. – Paul Jan 08 '18 at 10:41
  • 2
    @Paul I highly recommend reading these blog posts (https://rubberduckvba.wordpress.com/2016/06/16/oop-vba-pt-1-debunking-stuff/, https://rubberduckvba.wordpress.com/2016/07/05/oop-vba-pt-2-factories-and-cheap-hotels/) if that is how you feel about VBA. The thing is, while VBA doesn't support inheritance, it does support classes, functions, public, private, interfaces, etc. There's a whole host of ways to truly improve your code, and settling for the idea that there are problems that **need** more than one loop, or multiple `GoTo` statements only opens you up to bad code. – Brandon Barney Jan 08 '18 at 14:19
  • 2
    @Paul Making excuses for bad code (multiple for loops, goto statements to break from loops, not abstracting to functions, methods, and classes) only teaches a programmer that there is nothing left to learn, and that bad code is *okay* if the language is *functional enough*. That same behavior is what will set apart a strong employee from one who blames their problems on the language when their code breaks, versus accepting that they have more to learn. – Brandon Barney Jan 08 '18 at 14:22
  • 2
    @Paul Also consider checking out this excellent SO post: https://stackoverflow.com/questions/31858094/is-vba-an-oop-language-and-does-it-support-polymorphism/31861832#31861832. – Brandon Barney Jan 08 '18 at 14:25
  • 3
    @Paul Hi! I'm an "Excel coder" and I write polymorphic VBA code all the time. Nice to meet you! – Mathieu Guindon Jan 08 '18 at 14:26
  • @ScottCraner That's something I have done multiple times. From what I can tell the code should be working properly, and yet it doesn't. It's why I'm here. – PaulRey Jan 08 '18 at 15:58
  • @BrandonBarney Yeah, ignoring was maybe the wrong word to use. You're right, the first `If` statement is always evaluating to `True` , where I'm, lost is as to why. I have pretty much the same line of code in another macro and it works perfectly. – PaulRey Jan 08 '18 at 16:00
  • @PaulRey Check my answer. I think this is the problem (and I feel stupid for not seeing it well before now). – Brandon Barney Jan 08 '18 at 16:24
  • @BrandonBarney: the *average* person writing VBA stuff will not write in an object oriented way. I know this because I see them every day where I work. If I gave my time to help them achieve this principal then I would get no work done. – Paul Jan 08 '18 at 22:23
  • @MattsMugg: I did not know that VBA had come on so far. Mind you, it's 12+ years since I wrote in VBA. – Paul Jan 08 '18 at 22:26
  • @Paul If you don't write VBA, then I am uncertain how you can say what is 'Average' or not. This isnt the place for that discussion though. My point stands that multiple loops is a code smell (as it was in this case even), and that even the 'Average' programmer should strive for abstraction. Any less is making excuses for bad code. I am happy to discuss this further though in https://chat.stackexchange.com/rooms/14929/vba-rubberducking, and it is worth noting that the RubberDuck tool takes a lot of the heavy lifting out of cleaning code. – Brandon Barney Jan 09 '18 at 13:23

2 Answers2

1

I think this may be the source of your issue:

For p = 2 to LastRow3

    ' Note that we are looking in Column(6) for a value we are finding in 
    ' Cell(p, 6) which means that Column(6) will always contain the value
    ' (since we just took the value from column 6).
    If b.Columns(6).Find(d.Cells(p, 6), lookat:=xlWhole) Is Nothing Then
        Set FindRow = d.Columns(6).Find(a.Cells(p, 7))
        j = FindRow.Row
        If Not a.Columns(7).Find(d.Cells(p, 6), lookat:=xlWhole) Is Nothing Then

        End If
    End If
Next p

In essence, you have are using Find on Columns(6) with a value from Cells(p, 6). Since you are looking for a value in the same column that you are getting your value from, this condition will always be True except in the very rare instances where the value is removed between passing the value to Find and actually looking for it (very very unlikely scenario).

Brandon Barney
  • 2,382
  • 1
  • 9
  • 18
  • If you [edit] this answer so that the reader doesn't need to decipher the code to figure out what the problem (and its solution) is, you'll get my upvote :) – Mathieu Guindon Jan 08 '18 at 18:35
  • Good call. I was rushing off to a meeting when i found the issue initially. – Brandon Barney Jan 08 '18 at 18:43
  • @BrandonBarney The problem there is that it is looking in two different files. `b.columns(6)` and `d.Cells(p, 6)` apply to two different excel files, namely those set as `b` and `d` . The following `if` does the same but with `a` and `d` respectively. – PaulRey Jan 09 '18 at 09:24
0

Ignore my other answer. I missed that you were qualifying your two ranges with two different worksheets. Lets try this again, this time though we will use our debugging tools to see if we can figure out what is going on.

First, i'll provide some edited code, and then we can walk through it and see what the changes I've made are, as well as look at how debugging can help figure out what is happening:

Sub Foo()
    ' Descriptive names make your variables easy to follow.
    ' It is also best to declare your variables AS CLOSE to their first use as possible.
    ' Since below this point we start using Prueba, and then we get into the loop, this is the ideal location.
    With Application.Workbooks("Item ID Comparison")
        Dim FirstComparison As Worksheet
        Set FirstComparison = .Worksheets(1)

        Dim SecondComparison As Worksheet
        Set SecondComparison = .Worksheets(2)

        Dim ThirdComparison As Worksheet
        Set ThirdComparison = .Worksheets(3)
    End With

    Dim Prueba As Worksheet
    Set Prueba = Application.Workbooks("Prueba").Worksheets(1)

    ' Note here that, previously, 'Rows' was not properly qualified to 'Prueba' and thus actually refers to 'ActiveSheet.Rows.Count'
    'LastRow3 = d.Cells(Rows.Count, "F").End(xlUp).Row

    Dim LastPruebaRow As Long
    Prueba.Cells(Prueba.Rows.Count, "F").End(xlUp).Row

    ' By convention, we start with i and j as control variables
    Dim i As Long
    For i = 2 To LastPruebaRow
        Dim SearchResult As Range

        ' Note here as well that I specifically call on the value of the cell, not the cell itself.
        ' While the default member of a 'Cell' is 'Value' it is far better to be explicit than to be implicit.
        Set SearchResult = SecondComparison.Columns(6).Find(Prueba.Cells(i, 6).Value, lookat:=xlWhole)

        ' By separating out the concerns, you can look at the result of the find if it is successful, or use the immediate window
        ' to test different values of 'Find' until 'Not SearchResult Is Nothing'
        If SearchResult Is Nothing Then
            Dim FindRow As Range

            ' Again, specifically call on the 'Value' member
            Set FindRow = Prueba.Columns(6).Find(FirstComparison.Cells(i, 7).Value)

            Dim ResultRow As Long
            ResultRow = FindRow.Row

            ' And again, get 'Value' from the cell.
            If Not FirstComparison.Columns(7).Find(Prueba.Cells(i, 6).Value, lookat:=xlWhole) Is Nothing Then
                ' Note again that the row range is unqualified here, and actually is referring to 'ActiveSheet.Rows.Count'
                ' c.Cells(Rows.Count, "A").End(xlUp).Row

                With ThirdComparison
                    ' Since you are only ever using the index of the row + 1, just merge the two.
                    Dim LastComparisonRow As Long
                    LastComparisonRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1

                    .Range("A" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "A").Value
                    .Range("B" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "B").Value
                    .Range("C" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "C").Value
                    .Range("D" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "D").Value
                    .Range("E" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "E").Value
                    .Range("F" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "F").Value
                    .Range("G" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "G").Value
                    .Range("H" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "H").Value
                    .Range("I" & LastComparisonRow).Value = Prueba.Cells(ResultRow, "I").Value
                End With
            End If
        End If
    Next i
End Sub

The first thing I think is worth pointing out is that it is much easier to read what your code is doing (at least for others) when everything has a descriptive name. It didnt take me long to do this, or to come up with names that made sense, but it made it much easier to follow the flow of your code.

Likewise, all variable declaration is now happening as close to the first use as possible. I dont need to look at your Dim block at the top to know that LastPruebaRow is a long, it's right there (and its much easier to know what LastPruebaRow is over LastRow3).

Second, when using numeric variables for for loops the convention is to start with i. Thus:

For i = 1 to 10
    For j = 2 to 20
        For k = 3 to 30

        Next
    Next
Next

So on, so forth, though if you find yourself past j (and more often than not, past i) you likely need to refactor your code.

After adjusting the names and declarations, things started to pop out. For example:

LastRow3 = d.Cells(Rows.Count, "F").End(xlUp).Row

Isn't actually returning the last row of the Prueba worksheet. Instead, it is returning:

ThirdComparisonSheet.Cells(ActiveSheet.Rows.Count, "F").End(xlUp).Row

You also have this bit of code that doesn't conform to the rest:

Set FindRow = Prueba.Columns(6).Find(FirstComparison.Cells(i, 7).Value)

Notice how the others have lookat:=xlWhole and this one doesn't? This may be by design, so I left it, but it stuck out to me.

I also find it a little bit odd that your first conditional is looking for Nothing:

    ' Note here as well that I specifically call on the value of the cell, not the cell itself.
    ' While the default member of a 'Cell' is 'Value' it is far better to be explicit than to be implicit.
    Dim SearchValue as Variant
    SearchValue = Prueba.Cells(i, 6).Value

    Debug.Print "i :", i, "SearchValue :", SearchValue
    Dim SearchResult As Range
    Set SearchResult = SecondComparison.Columns(6).Find(SearchValue, lookat:=xlWhole)
    If SearchResult Is Nothing Then

Lastly, as you can also see in the above example, the loop will print out i and SearchValue on every iteration (use this with caution, I highly recommend using a breakpoint, particularly with large worksheets).

In all of this, we should have a much easier time finding the problem (if it hasnt been surfaced already).

Brandon Barney
  • 2,382
  • 1
  • 9
  • 18
  • So, I took a look at the code and I made multiple attempts to make it work, and I finally figured out the problem, it had nothing to do with the `If` statements. Those were working perfectly fine. The problem was `Set FindRow = Prueba.Columns(6).Find(FirstComparison.Cells(i, 7).Value)` . This was a stupid mistake from my part, as that whole part of the code was not needed. Thanks for your help, and for explaining some VBA conventions. I am completely self-taught in VBA, so I'll keep those in mind. – PaulRey Jan 10 '18 at 15:56
  • Glad to hear that you figured it out. Definitely do your part to implement better conventions. It will make these kinds of problems stick out like a sore thumb :). – Brandon Barney Jan 10 '18 at 15:58