Excel VBA 自定义MyXLOOKUP函数

本文于2023年6月11日首发于本人同名公众号:Excel活学活用,更多文章案例请搜索关注!

☆本期内容概要☆

  • 自定义函数MyXLOOKUP()

大家好,我是冷水泡茶,随着OFFICE不断升级,新的函数也时有出现,比如XLOOKUP,曾经一度火爆全网(我说的是不是有点夸张了?也许,但各大博主争相推荐倒是不假。)说什么彻底取代VLOOKUP云云。

今天我们就来看一下这个查询利器,我上网搜了一搜,找到微软官网上的一篇文章:XLOOKUP 函数 - Microsoft 支持,介绍得非常详细,我就不画蛇添足了,感兴趣的朋友可以移步第二条推文。

XLOOKUP好是好,然,低版本的OFFICE并不能用,光看着眼馋没办法。

难道真的就这样算了吗?继续用我们的VLOOKUP?

来来来,我们有VBA啊,我们自己搞一个试试,看能不能实现XLOOKUP的功能。

其实这个问题我老早就想过了,只是一直没有功夫来做。今天得空,我们来偿试一把。

XLOOKUP的参数比较多,但基本的有3个,是必填项:

1、lookup_value:要搜索的值

2、lookup_array:要搜索的数组或区域

3、return_array:要返回的数组或区域

到这问题基本就明朗了,就是循环数组,找到相同的值,然后把另一个数组对应位置的值作为函数的结果

但动手写代码总是有点头疼的,还是把问题交给ChatGPT吧,让它写一个类似XLOOKUP的自定义函数:

Function MyXLOOKUP1(lookup_value As Variant, _
    lookup_range As Range, _
    return_range As Range) As Variant
    Dim lookup_array As Variant
    Dim return_array As Variant
    Dim i As Long
    lookup_array = lookup_range.Value
    return_array = return_range.Value
    For i = 1 To UBound(lookup_array, 1)
        If lookup_array(i, 1) = lookup_value Then
            MyXLOOKUP1 = return_array(i, 1)
            Exit Function
        End If
    Next i
    MyXLOOKUP1 = CVErr(xlErrNA) ' 如果没有找到匹配项,则返回#N/A错误
End Function

这里有个CVErr函数,还是第一次遇到,再问AI吧,AI就是好,它总是不厌其烦回答你的问题:

CVErr是一个VBA函数,用于创建表示特定错误类型的Variant类型的值。它用于在VBA代码中处理错误。

CVErr 函数接受一个整数参数,该参数对应于不同的错误类型。以下是一些常见的 CVErr 错误类型:

xlErrDiv0 (错误码: 2007):表示除零错误。

xlErrNA (错误码: 2042):表示找不到值或无效结果。

xlErrName (错误码: 2029):表示无效的函数或命名范围。

xlErrNull (错误码: 2000):表示无效的空值。

xlErrNum (错误码: 2036):表示无效的数值。

xlErrRef (错误码: 2023):表示无效的引用。

使用CVErr 函数可以将这些错误类型的值分配给变量,以在程序中进行错误处理或返回给调用者。如,CVErr(xlErrNA) 会创建一个代表找不到值或无效结果的Variant 类型的值。

请注意,CVErr函数的返回值是一种特殊的数据类型 Error,如果将其赋给变量,需要使用 Variant 类型来接收该值。

这些东西谁能记得?先记录下来备用吧。

函数代码不复杂,测试可用。但XLOOKUP后面还有一串参数呢,咋整?这次再问AI,结果就没那么理想了,具体过程不说了,我自己改吧,最后基本完成,我测试了一下,基本达到预期:

函数完整代码如下:

Function MyXLOOKUP(Lookup_Value, _
        Lookup_Array, Return_Array, _
        Optional if_Not_Found, _
        Optional match_Mode As Integer = 0, _
        Optional Search_Mode As Integer = 1)
    Dim Lookup_Values
    Dim Return_Values
    Dim Result
    t = 0
    temp = ""
    If TypeOf Lookup_Value Is Range Then
        Lookup_Values = Lookup_Array.Value
    Else
        Lookup_Values = Lookup_Array
    End If
    If TypeOf Return_Values Is Range Then
        Return_Values = Return_Array.Value
    Else
        Return_Values = Return_Array
    End If
    If match_Mode = 0 Then
        '精确匹配
        If Search_Mode = 1 Then
            For i = LBound(Lookup_Values, 1) To UBound(Lookup_Values, 1)
                If Lookup_Values(i, 1) = Lookup_Value Then
                    Result = Return_Values(i, 1)
                    Exit For
                End If
            Next
        Else
            For i = UBound(Lookup_Values, 1) To LBound(Lookup_Values, 1) Step -1
                If Lookup_Values(i, 1) = Lookup_Value Then
                    Result = Return_Values(i, 1)
                    Exit For
                End If
            Next
        End If
    ElseIf match_Mode = -1 Then
        '精确匹配,若无匹配,则返回第一个最接近且比查找值小的值。
        If Search_Mode = 1 Then
            For i = LBound(Lookup_Values, 1) To UBound(Lookup_Values, 1)
                If Lookup_Values(i, 1) = Lookup_Value Then
                    Result = Return_Values(i, 1)
                    t = 1
                    Exit For
                End If
            Next
        Else
            For i = UBound(Lookup_Values, 1) To LBound(Lookup_Values, 1) Step -1
                If Lookup_Values(i, 1) = Lookup_Value Then
                    Result = Return_Values(i, 1)
                    t = 1
                    Exit For
                End If
            Next
        End If
        If t = 0 Then
            If Search_Mode = 1 Then
                For i = LBound(Lookup_Values, 1) To UBound(Lookup_Values, 1)
                    If Lookup_Values(i, 1) < Lookup_Value Then
                        If Lookup_Values(i, 1) > temp Then
                            temp = Lookup_Values(i, 1)
                            Result = Return_Values(i, 1)
                        End If
                    End If
                Next
            Else
                For i = UBound(Lookup_Values, 1) To LBound(Lookup_Values, 1) Step -1
                    If Lookup_Values(i, 1) < Lookup_Value Then
                        '
                        If Lookup_Values(i, 1) > temp Then
                            temp = Lookup_Values(i, 1)
                            Result = Return_Values(i, 1)
                        End If
                    End If
                Next
            End If
        End If
    Else
        '精确匹配,若无匹配,则返回第一个最接近查找值的较其大值。
        If Search_Mode = 1 Then
            For i = LBound(Lookup_Values, 1) To UBound(Lookup_Values, 1)
                If Lookup_Values(i, 1) = Lookup_Value Then
                    Result = Return_Values(i, 1)
                    t = 1
                    Exit For
                End If
            Next
        Else
            For i = UBound(Lookup_Values, 1) To LBound(Lookup_Values, 1) Step -1
                If Lookup_Values(i, 1) = Lookup_Value Then
                    Result = Return_Values(i, 1)
                    t = 1
                    Exit For
                End If
            Next
        End If
        If t = 0 Then
            If Search_Mode = 1 Then
                For i = LBound(Lookup_Values, 1) To UBound(Lookup_Values, 1)
                    If Lookup_Values(i, 1) > Lookup_Value Then
                        If temp = "" Then
                            temp = Lookup_Values(i, 1)
                            Result = Return_Values(i, 1)
                        End If
                        If Lookup_Values(i, 1) < temp Then
                            temp = Lookup_Values(i, 1)
                            Result = Return_Values(i, 1)
                        End If
                    End If
                Next
            Else
                For i = UBound(Lookup_Values, 1) To LBound(Lookup_Values, 1) Step -1
                    If Lookup_Values(i, 1) > Lookup_Value Then
                        If temp = "" Then
                            temp = Lookup_Values(i, 1)
                            Result = Return_Values(i, 1)
                        End If
                        If Lookup_Values(i, 1) < temp Then
                            temp = Lookup_Values(i, 1)
                            Result = Return_Values(i, 1)
                        End If
                    End If
                Next
            End If
        End If
    End If
    If IsEmpty(Result) Then
        If Not IsMissing(if_Not_Found) Then
            Result = if_Not_Found
        Else
            Result = CVErr(xlErrNA)
        End If
    End If
    MyXLOOKUP = Result
End Function

代码解析:

1、默认模式:循环目标区域数组,找匹配值,找到匹配值,则返回return_array中对应的值,找不到返回错误值。

2、match_mode=-1,表示精确匹配,若匹配不到则找一个比查找值小的且最接近查找值的值,返回return_array中对应的值,找不到返回错误值。

这里首先进行循环查找,如果查到,则使t=1,退出循环。接着进行判断,如果t=0,则表明没有精确匹配到,这时我们要找一个较小值。

3、match_mode=1 或者其他值,找较大的,其他同上。

4、search_mode,默认为1,从前往后查,其他值,从后往前查。这里两种search_mode的区别在于,当有lookup_array中有相同的值,而return_array中对应的值却不同,则会使得两种查询方式的结果不同。

5、代码经简单测试通过,可能存在BUG。目前该自定义函数仅适用纵向查找。lookup_array,return_array均为同等大小的纵向单列区域。

6、以上代码仅为研究测试VBA编码功能之用,若有朋友用于数据查询分析的,请谨慎参考。建议尽量使用EXCEL内置函数,以免给工作带来不必要的麻烦。

好,今天就到这吧。请大家多多点赞、留言、分享,谢谢大家,我们下期再会

本文于2023年6月11日首发于本人同名公众号:Excel活学活用,更多文章案例请搜索关注!


举报
评论 0