构build一个用于多键查找的快速Excel VBA UDF

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

有点像VLOOKUP的多键版本。 我知道你可以创build一个单一的键,由第1栏中的各个键组成,但是我想避免这样做。

我已经创build了这个函数的几个版本,到目前为止最好的版本工作正常,除了它很 ! 我的一个表格是接近9000行11列,我正在使用一个6字段的键。 我有大约18000次这个公式,重新计算工作表大约需要3分钟(我计算出有9.7亿个计算公式,所以当然会很慢)。

它使用了Evaluate()和我所见过的许多解决scheme。 代码如下:

 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()的解决scheme,因为我不想预先计算组合键。

我也想避免SUMPRODUCT解决scheme只能用于数字,不返回第一个值,但如果find多个匹配,则总结所有值。

即使我知道有一些好的存在,我也不能依赖第三方插件。

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

不过,我在尝试删除Evaluate()失败了。 任何人都可以帮我吗?

看起来VBA中的WorksheetFunction.Index() / Match()只支持单个范围和单个键,除非有人能解释如何实现那个可爱的(range1=key1)*(range2=key2)...表示MATCH函数在工作表是有福的,但WorksheetFunction.Match()莫名其妙不是。

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

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

这给了我们一个直接的好处。 当然,如果你没有其他钥匙,可以select一个可变的支票而不是五个,这是一个很大的飞跃。 但是,如果你一次使用它6个完整的键,我将会看到一个完全不同的代码。 Evaluate是一个巨大的杀手,如果你是你的UDF每次重新计算(即Application.Volatile )的types,你的计算时间将会更加巨大。

为了显示一个非常小的变化,这里是我的代码( 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 

祝你好运,如果你想继续沿着这条路。 🙂

查看.NET(或其他),将大部分代码重构为外部组件。 将UDF更改为简单地将范围转换为2维对象数组并将其传递。 使用返回值作为返回到UDF函数的返callback用的值。

其他任何东西都不会显着减less处理时间。

这是进程内计算引擎的限制。 它与纯math运作良好,但与Excel对象或其function(原始以外)的工作往往会受到巨大的打击。 甚至有一篇关于Excel UDF限制的kb文章。 值得一读。

Excel中自定义函数的限制说明