Why does my spreadsheet function behave differentl

2019-01-28 03:25发布

When I call the function from Excel (in a cell):

=allVlookup(O24,A:D,3,"")

vs via vba

MsgBox allVlookup(Range("O24"), Range("A:D"), 3, "")

I get different results. When called from Excel, I only get the first match, but when calling from a vba test sub with identical parameters (except adding Range to the arguments to allow the sub to run), I get the full results (which is more than one).

The function I am using is:

Public Function allVlookup(lookupRange As Range, tableRange As Range, colIndex As Integer, Optional delimiter As String = "") As String

    Dim c As Range
    Dim firstAddress As String
    'MsgBox tableRange.Address  ' this is correct
    'With Sheets(4).Range("A1:C12").Columns(1)
    'With Range("A1:C12").Columns(1)

    'this doesn't allow things to work right either (???)
    'Set tableRange = Range("A:D")
    'Set lookupRange = Range("O24")

    'search only the first column for matches
    With tableRange.Columns(1)
        Set c = .Find(what:=lookupRange.Value, LookIn:=xlValues)

        If Not c Is Nothing Then

            firstAddress = c.Address

            Do
                'add the delimiter
                If (allVlookup <> "") Then
                    allVlookup = allVlookup + delimiter
                End If

                'append value to previous value
                allVlookup = allVlookup + c.Offset(0, colIndex).Value


                Set c = .FindNext(c)
                'exit conditions
                'no match found
                If (c Is Nothing) Then
                    Exit Do
                    'we're back to start
                ElseIf (c.Address = firstAddress) Then
                    Exit Do
                End If

            Loop
        End If
    End With

End Function

I am at a loss to explain why this is happening.

What can I do to get the outputs to be identical?

2条回答
兄弟一词,经得起流年.
2楼-- · 2019-01-28 04:17

The reason why it only gives the first match is because of a bug. Please see this link (SECTION 5) at the bottom.

I have already filed it as a bug long time ago. If you read the above link then I have suggested an alternative code as well.

Extract from that link in case the link ever dies (which it shouldn't)

.FindNext doesn’t work in a User-Defined Function as expected. You can use it in a normal function.

Lets Say We have this data in Sheet1:

A1 → Colt
A2 → Holt
A3 → Dolt
A4 → Hello

and in

B1 → olt

Now if we paste the below code in a module and run it then we will get the expected result as $A$1:$A$3

Sub Test()
    Sample Sheets("Sheet1").Range("B1"), Sheets("Sheet1").Range("A1:A4")
End Sub

Sub Sample(FirstRange As Range, ListRange As Range)
    Dim aCell As Range, bCell As Range, oRange As Range
    Dim ExitLoop As Boolean
    Set oRange = ListRange.Find(what:=FirstRange.Value, LookIn:=xlValues, _
    lookat:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
    MatchCase:=False, SearchFormat:=False)
    ExitLoop = False
    If Not oRange Is Nothing Then
        Set bCell = oRange: Set aCell = oRange

        Do While ExitLoop = False
            Set oRange = ListRange.FindNext(After:=oRange)

            If Not oRange Is Nothing Then
                If oRange.Address = bCell.Address Then Exit Do
                Set aCell = Union(aCell, oRange)
            Else
                ExitLoop = True
            End If
        Loop
        MsgBox aCell.Address
    Else
        MsgBox "Not Found"
    End If
End Sub

However it would not work as expected if you paste this function in a module and call it from a worksheet as (Say in Cell C1) =FindRange(A1,A1:A5)

The code will only give you the 1st instance of the value found and ignore the rest

And hence the result that you will get is $A$2!!!

Function FindRange(FirstRange As Range, ListRange As Range) As String
    Dim aCell As Range, bCell As Range, oRange As Range
    Dim ExitLoop As Boolean
    Set oRange = ListRange.Find(what:=FirstRange.Value, LookIn:=xlValues, _
    lookat:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
    MatchCase:=False, SearchFormat:=False)
    ExitLoop = False
    If Not oRange Is Nothing Then
        Set bCell = oRange: Set aCell = oRange

        Do While ExitLoop = False
            Set oRange = ListRange.FindNext(After:=oRange)

            If Not oRange Is Nothing Then
                If oRange.Address = bCell.Address Then Exit Do
                Set aCell = Union(aCell, oRange)
            Else
                ExitLoop = True
            End If
        Loop
        FindRange = aCell.Address
    Else
        FindRange = "Not Found"
    End If
End Function

We need to approach this from a different angle.

Instead of using .FindNext we use .Find again till we get the desired result ($A$1:$A$3). See the code below which works:

Function FindRange(FirstRange As Range, ListRange As Range) As String
    Dim aCell As Range, bCell As Range, oRange As Range
    Dim ExitLoop As Boolean
    Set oRange = ListRange.Find(what:=FirstRange.Value, LookIn:=xlValues, _
    lookat:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
    MatchCase:=False, SearchFormat:=False)
    ExitLoop = False
    If Not oRange Is Nothing Then
        Set bCell = oRange: Set aCell = oRange

        Do While ExitLoop = False
            Set oRange = ListRange.Find(what:=FirstRange.Value, After:=oRange, LookIn:=xlValues, _
            lookat:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
            MatchCase:=False, SearchFormat:=False)
            If Not oRange Is Nothing Then
                If oRange.Address = bCell.Address Then Exit Do
                Set aCell = Union(aCell, oRange)
            Else
                ExitLoop = True
            End If
        Loop
        FindRange = aCell.Address
    Else
        FindRange = "Not Found"
    End If
End Function
查看更多
Emotional °昔
3楼-- · 2019-01-28 04:25

Please, don't as for logic but here is solution which gives me the same results. Change .Find line into:

Set c = .Find(what:=lookupRange.Value2, after:=.Cells(1), LookIn:=xlValues, LookAt:=xlWhole)

and additionally change .FindNext into:

Set c = .Find(what:=lookupRange.Value2, after:=c, LookIn:=xlValues, LookAt:=xlWhole)

Pleas keep also in mind that tableRange range should have column titles. If not, the results order would not be as expected at the first sight.

Additional (EDITED) explanation for last sentence. If you have table of that type:

    |  A  |  B  |  C  |  D  |
  --+-----+-----+-----+-----+
  1 | ABC    1     2     A
  2 | ABC    3     4     B
  3 | ABC    5     6     C

when searching of ABC in range("A1:D3") to get data from column D you would get as result: BCD. To get ABC there should be column titles in the first row.

查看更多
登录 后发表回答