构建快速Excel VBA UDF以进行多键查找

时间:2013-11-13 00:40:53

标签: performance excel vba excel-vba excel-udf

我在一个工作簿中有几个非常大的数据表,需要使用Excel用户定义的函数来查找这些表中的数据。我需要能够提供可变数量的键列和键值来搜索,并且函数需要找到第一个匹配的行,然后使用此偏移量来查找相应数据列中的值。

有点像VLOOKUP的多键版本。我知道你可以创建一个单独的密钥,由第1列中附加在一起的各个密钥组成,但我希望避免这样做。

我已经创建了这个函数的几个版本,到目前为止最好的一个工作正常,除了它是!我的一个表是近9,000行乘11列,我使用的是6字段键。我有大约18,000次出现这个公式并且重新调整工作表大约需要3分钟(我计算出涉及9.7亿个计算,所以当然会很慢)。

它使用了Evaluate()我见过的许多解决方案。这是代码:

Function KeyLookup(datatable As Variant, datacol As String, _
                   key1table As Variant, key1 As String, _
                   Optional key2table As Variant, Optional key2 As String, _
                   Optional key3table As Variant, Optional key3 As String, _
                   Optional key4table As Variant, Optional key4 As String, _
                   Optional key5table As Variant, Optional key5 As String, _
                   Optional key6table As Variant, Optional key6 As String) As Variant
    Dim cmd As String

    cmd = "INDEX(" & datatable.Address & ",MATCH(1,("
    cmd = cmd & key1table.Address & "=""" & key1 & """)"
    If Not IsMissing(key2table) Then cmd = cmd & "*(" & key2table.Address & "=""" & key2 & """)"
    If Not IsMissing(key3table) Then cmd = cmd & "*(" & key3table.Address & "=""" & key3 & """)"
    If Not IsMissing(key4table) Then cmd = cmd & "*(" & key4table.Address & "=""" & key4 & """)"
    If Not IsMissing(key5table) Then cmd = cmd & "*(" & key5table.Address & "=""" & key5 & """)"
    If Not IsMissing(key6table) Then cmd = cmd & "*(" & key6table.Address & "=""" & key6 & """)"
    cmd = cmd & ",0)," & datacol & ")"

    KeyLookup = Evaluate(cmd)
End Function

这会生成如下所示的cmd值:

INDEX($K$3:$L$8993,MATCH(1,($B$3:$B$8993="a1-5")*($C$3:$C$8993="Tarp")*($E$3:$E$8993="Sydney")*($F$3:$F$8993="Highest Reach")*($G$3:$G$8993="1+")*($J$3:$J$8993="T0"),1),1)

我需要一些帮助才能尽快做到这一点。 3分钟太慢了。

如上所述,我想避免使用基于VLOOKUP()的解决方案,因为我不想预先计算组合键。

我还想避免只使用数字的SUMPRODUCT解决方案,如果找到多个匹配项,则不返回第一个值,而是将所有值相加。

我也不能依赖第三方加载项,即使我知道存在一些好的加载项。

所以,我目前的想法是本地使用WorksheetFunction.Index() / Match(),因此删除Evaluate()因为我知道这会增加很大的开销。

但是,我尝试删除Evaluate()时失败了。谁能在这帮助我?

VBA中的WorksheetFunction.Index() / Match()似乎只支持单个范围和单个密钥,除非有人可以解释如何实现(range1=key1)*(range2=key2)...函数的MATCH符号工作表很幸运,但WorksheetFunction.Match()不知道。

2 个答案:

答案 0 :(得分:0)

非常好的帖子,虽然没有真正的问题。 ;)在编写代码时,像IF这样的条件检查会使执行时间变得非常糟糕,但通常这是检查的最可靠方法。在上面的代码中,每次使用时都会检查IF Not IsMissing条件 5次。这会导致每次检查的负载呈指数级增长(尽管我无法真正告诉您多少)。

如果不过多编辑代码,可以应用一个逻辑来一次跳过5个检查。而不是检查是否存在, 检查是否存在 。基本上,您的公式有一个可选的key2。如果key2不存在,那么key3... key6也不会。遵循此模式,如果key3不存在,则key4... key6也不会。

这给了我们一个直接的优势。当然,当你没有其他密钥时,可变检查而不是五次是一个很大的飞跃。但是,如果您一次使用6个完整密钥,我将查看完全不同的代码。 Evaluate是一个巨大的杀手,如果你是每次重新计算你的UDF(即Application.Volatile)的类型,你的计算时间将会受到更大的打击。

为了显示非常次要更改,这是我对您的代码的看法( UNTESTED ):

Function KeyLookup(datatable As Variant, datacol As String, _
                   key1table As Variant, key1 As String, _
                   Optional key2table As Variant, Optional key2 As String, _
                   Optional key3table As Variant, Optional key3 As String, _
                   Optional key4table As Variant, Optional key4 As String, _
                   Optional key5table As Variant, Optional key5 As String, _
                   Optional key6table As Variant, Optional key6 As String) As Variant
    Dim cmd As String

    cmd = "INDEX(" & datatable.Address & ",MATCH(1,(" & key1table.Address & "=""" & key1 & """)"
    cmd2 = ",0)," & datacol & ")"

    If IsMissing(key2table) Then GoTo SkipOthers
    ElseIf IsMissing(key3table) Then
        cmd = cmd & "*(" & key2table.Address & "=""" & key2 & """)"
        GoTo SkipOthers
    ElseIf IsMissing(key4table) Then
        cmd = cmd & "*(" & key3table.Address & "=""" & key3 & """)"
        GoTo SkipOthers
    ElseIf IsMissing(key5table) Then
        cmd = cmd & "*(" & key4table.Address & "=""" & key4 & """)"
        GoTo SkipOthers
    ElseIf IsMissing(key6table) Then
        cmd = cmd & "*(" & key5table.Address & "=""" & key5 & """)"
        GoTo SkipOthers
    Else
        cmd = cmd & "*(" & key6table.Address & "=""" & key6 & """)"
    End If

SkipOthers:
    KeyLookup = Evaluate(cmd & cmd2)

End Function

祝你好运,如果你想继续这条道路。 :)

答案 1 :(得分:0)

研究.NET(或替代方案),将大部分代码重构为外部组件。更改UDF以简单地将范围转换为2维对象数组并将其传递通过。使用返回值作为值来传递回UDF函数的返回调用。

其他任何事情都不会显着缩短处理时间。

这是进程内计算引擎的限制。它适用于纯数学,但使用excel对象或其函数(除了原语)往往会受到重创。 甚至还有关于Excel UDF限制的kb文章。值得一读。

Description of limitations of custom functions in Excel