如何优化vlookup以应对高搜索量?(VLOOKUP的替代方案)

23

我正在寻找vlookup的替代方案,在感兴趣的上下文中具有更好的性能。

背景如下:

  • 我有一个包含{键;数据}的数据集,数据集很大(~ 100,000条记录)
  • 我想在数据集上执行大量的VLOOKUP操作(典型用法是重新排序整个数据集)
  • 我的数据集没有重复的键
  • 我只寻找精确匹配(VLOOKUP的最后一个参数为FALSE

解释图示:

参考表格:("sheet1"

        A           B
     1
     2  key1        data1
     3  key2        data2
     4  key3        data3
   ...  ...         ...
 99999  key99998    data99998
100000  key99999    data99999
100001  key100000   data100000
100002

查找表格:

        A           B
     1
     2  key51359    =VLOOKUP(A2;sheet1!$A$2:$B$100001;2;FALSE)
     3  key41232    =VLOOKUP(A3;sheet1!$A$2:$B$100001;2;FALSE)
     4  key10102    =VLOOKUP(A3;sheet1!$A$2:$B$100001;2;FALSE)
   ...  ...         ...
 99999  key4153     =VLOOKUP(A99999;sheet1!$A$2:$B$100001;2;FALSE)
100000  key12818    =VLOOKUP(A100000;sheet1!$A$2:$B$100001;2;FALSE)
100001  key35032    =VLOOKUP(A100001;sheet1!$A$2:$B$100001;2;FALSE)
100002

在我的Core i7 M 620 @2.67 GHz上,这个计算大约需要10分钟

在这种情况下,是否有比VLOOKUP性能更好的替代方案?


如果数据集很大,那么Excel可能不是一个好的选择。使用像*SQL或MS Access这样的专业数据库会更好。 - phuclv
4个回答

23

我考虑了以下几种选择:

  • VLOOKUP 数组公式
  • MATCH / INDEX
  • VBA(使用字典)

它们的性能比较如下:

  • VLOOKUP 简单公式:约10分钟
  • VLOOKUP 数组公式:约10分钟(1:1 的性能指数)
  • MATCH / INDEX:约2分钟(5:1 的性能指数)
  • VBA(使用字典):约6秒钟(100:1 的性能指数)

在相同的参考表中进行查找

1)查找表格:(vlookup 数组公式版本)

         A          B
     1
     2   key51359    {=VLOOKUP(A2:A10001;sheet1!$A$2:$B$100001;2;FALSE)}
     3   key41232    formula in B2
     4   key10102    ... extends to
   ...   ...         ... 
 99999   key4153     ... cell B100001
100000   key12818    ... (select whole range, and press
100001   key35032    ... CTRL+SHIFT+ENTER to make it an array formula)
100002

2)查询表格:(匹配+索引版本)

         A           B                                       C
      1
      2  key51359    =MATCH(A2;sheet1!$A$2:$A$100001;)       =INDEX(sheet1!$B$2:$B$100001;B2)
      3  key41232    =MATCH(A3;sheet1!$A$2:$A$100001;)       =INDEX(sheet1!$B$2:$B$100001;B3)
      4  key10102    =MATCH(A4;sheet1!$A$2:$A$100001;)       =INDEX(sheet1!$B$2:$B$100001;B4)
    ...  ...         ...                                     ...
  99999  key4153     =MATCH(A99999;sheet1!$A$2:$A$100001;)   =INDEX(sheet1!$B$2:$B$100001;B99999)
 100000  key12818    =MATCH(A100000;sheet1!$A$2:$A$100001;)  =INDEX(sheet1!$B$2:$B$100001;B100000)
 100001  key35032    =MATCH(A100001;sheet1!$A$2:$A$100001;)  =INDEX(sheet1!$B$2:$B$100001;B100001)
 100002

3)查询表格:(vbalookup版本)

       A          B
     1
     2  key51359    {=vbalookup(A2:A50001;sheet1!$A$2:$B$100001;2)}
     3  key41232    formula in B2
     4  key10102    ... extends to
   ...  ...         ...
 50000  key91021    ... 
 50001  key42       ... cell B50001
 50002  key21873    {=vbalookup(A50002:A100001;sheet1!$A$2:$B$100001;2)}
 50003  key31415    formula in B50001 extends to
   ...  ...         ...
 99999  key4153     ... cell B100001
100000  key12818    ... (select whole range, and press
100001  key35032    ... CTRL+SHIFT+ENTER to make it an array formula)
100002

NB:由于某些(内外部)原因,vbalookup一次无法返回超过65536个数据。因此我不得不将数组公式拆分成两份。

以及相关的VBA代码:

Function vbalookup(lookupRange As Range, refRange As Range, dataCol As Long) As Variant
  Dim dict As New Scripting.Dictionary
  Dim myRow As Range
  Dim I As Long, J As Long
  Dim vResults() As Variant

  ' 1. Build a dictionnary
  For Each myRow In refRange.Columns(1).Cells
    ' Append A : B to dictionnary
    dict.Add myRow.Value, myRow.Offset(0, dataCol - 1).Value
  Next myRow

  ' 2. Use it over all lookup data
  ReDim vResults(1 To lookupRange.Rows.Count, 1 To lookupRange.Columns.Count) As Variant
  For I = 1 To lookupRange.Rows.Count
    For J = 1 To lookupRange.Columns.Count
      If dict.Exists(lookupRange.Cells(I, J).Value) Then
        vResults(I, J) = dict(lookupRange.Cells(I, J).Value)
      End If
    Next J
  Next I

  vbalookup = vResults
End Function

NB: Scripting.Dictionary需要一个对Microsoft Scripting Runtime的引用,这必须手动添加(在Excel VBA窗口中的Tools->References菜单)。

结论:

在这种情况下,使用字典的VBA比使用VLOOKUP快100倍,比MATCH/INDEX快20倍。


3
我正试图使用你的vbalookup来替代我的Excel中vlookup的功能。我有一个标签页中的数据和其他标签页中的各种公式。如果我理解正确,我应该使用与vlookup相同的语法:vlookup(值,范围,列)。现在问题来了:它总是返回#value作为结果。您是否遇到过相同的情况? - fatSlave
1
你可以使用 Set dict = CreateObject("Scripting.Dictionary") 来代替将其声明为 scripting.dictionary,这样就不需要额外的 VBA 引用,并且代码与默认的 VBA 安装兼容。 - Nick van H.
如果有人找到了fatSlave的问题的答案,请在此处发布: https://stackoverflow.com/questions/48209023/avoid-the-value-error-when-using-a-vlookup-with-scrypting-dictionary - Pierre44

6

您还可以考虑使用“双重Vlookup”方法(不是我的主意 - 在其他地方看到过)。 我在工作表2上对100,000个查找值进行了测试(随机排序),并使用与工作表1上描述的相同数据集计时为不到4秒。 该代码也更简单。

Sub FastestVlookup()

    With Sheet2.Range("B1:B100000")
        .FormulaR1C1 = _
        "=IF(VLOOKUP(RC1,Sheet1!R1C1:R100000C1,1)=RC1,VLOOKUP(RC1,Sheet1!R1C1:R100000C2,2),""N/A"")"
        .Value = .Value
    End With

End Sub

你能解释一下你的代码吗?需要做哪些更改? - davejal
所有字段都会获得这个值:"#NAME?"。 - davejal
如果你遇到了“#NAME?”错误,很可能是你在代码中拼写了错误的“VLOOKUP”,或者你把一个“(”放错了位置。请检查并重试。 - user3259118

5

切换到Excel 2013并使用数据模型。 在那里,您可以在两个表中定义具有唯一ID键的列,并在数据透视表中将这两个表绑定为关系。 然后,如果绝对必要,您可以使用Getpivotdata()填充第一个表格。 我曾经在类似的 ~250K行表格中进行vlookup,在一个小时后停止了Excel计算。使用数据模型只需不到10秒。


-1

数值修复:在构建字典时检查空单元格。如果单元格为空,则退出循环。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接