用VBA内存数组replace工作表数组公式

我的工作表需要BG2中的以下数组公式。

=INDEX('Client'!O$2:O$347473, MATCH(1, (('Client_Cost'!D$2:D$347473='Client'!BC2)* ('Client_Cost'!E$2:E$347473='Client'!BE2)), 0)) 

这提供了两列匹配(Client_Cost!D:D到Client!BC2和Client_Cost!E:E到Client!BE2),并从Client!O:O返回相应的值。

大量的行使得数组公式非常计算密集。 我可以处理几百行(500行约90秒),但是我需要一直到客户端BG347473的结果,我希望在今年的某个时候。

我已经尝试使用Application Evaluate从数组公式返回结果数组到变体数组,然后返回结果数组到集体工作表,但这不是我期待的改进。 寻找替代品。

首先,我会build议开发一个更小的数据集替代方法。 5K或10K行将显示出明显的改善与否; 一旦您确信自己不会进入一个漫长的“无响应”状态,那么您可以随时扩展到原始数据集。

从数组公式1中删除数组的常用方法是“帮助程序”列,它将Client_Cost工作表中列D和E的两个值连接成单个分隔值。 例如,在Client_Cost!Z2中,

 =CONCATENATE(Client_Cost!D2, "|", Client_Cost!E2) 

填充到Client_Cost!Z347473应该只需要一两秒钟。

一旦build立起来,一个INDEX / MATCH函数对可以在类似的连接的Client!BC2和Client'!BE2上提供更高效的查找。 在Client!BG2中,

 =INDEX(Client!O$2:O$347473, MATCH(CONCATENATE(Client!BC2, "|", Client!BE2), Client_Cost'!Z$2:Z$347473, 0)) 

这将需要1小时51分钟为350K行。 尽pipe还不是最佳的,但是在原来估计的17.5小时内,这是一个很大的改进。

优化该方法的下一个逻辑步骤是使用VBA Scripting.Dictionary对象。 一个字典在其关键字上保存着自己唯一的索引,连接的值可以被塞进一个字典对象中,以便在大量的项目(即 )上实时地进行查询。

 Sub JR_CSE_in_Array() Dim olr As Long, rws As Long, JR_Count As Long, JR_Values As Variant Dim v As Long, vTMP As Variant, vTMPs As Variant, dVALs As Object Debug.Print Timer Set dVALs = CreateObject("Scripting.Dictionary") 'get some dimensions to the various data ranges With Worksheets("Client_Cost") 'only use as many rows as absolutely necessary olr = Application.Min(.Cells(Rows.Count, "D").End(xlUp).Row, _ .Cells(Rows.Count, "E").End(xlUp).Row) 'store D & E vTMPs = .Range(.Cells(2, 4), .Cells(olr, 5)).Value2 End With With Worksheets("Client") rws = Application.Min(.Cells(Rows.Count, "BC").End(xlUp).Row, _ .Cells(Rows.Count, "BE").End(xlUp).Row, _ UBound(vTMPs, 1)) 'override the above statement for sampling 'rws = 5000 'building the Dictionary object takes a fair bit of time but it is worth it vTMP = .Range(.Cells(2, 15), .Cells(olr, 15)).Value2 For v = LBound(vTMPs, 1) To UBound(vTMPs, 1) If Not dVALs.Exists(Join(Array(vTMPs(v, 1), vTMPs(v, 2)), ChrW(8203))) Then _ dVALs.Add Key:=Join(Array(vTMPs(v, 1), vTMPs(v, 2)), ChrW(8203)), Item:=vTMP(v, 1) Next v 'store BC and BE vTMPs = .Range(.Cells(2, 55), .Cells(olr, 57)).Value2 End With ReDim JR_Values(1 To rws, 1 To 1) 'force a two-dimension, one-based index on the array 'Debug.Print LBound(JR_Values) & ":" & UBound(JR_Values) For JR_Count = LBound(JR_Values, 1) To UBound(JR_Values, 1) Step 1 If dVALs.Exists(Join(Array(vTMPs(JR_Count, 1), vTMPs(JR_Count, 3)), ChrW(8203))) Then JR_Values(JR_Count, 1) = dVALs.Item(Join(Array(vTMPs(JR_Count, 1), vTMPs(JR_Count, 3)), ChrW(8203))) End If Next JR_Count With Worksheets("Client") .Range("BG2").Resize(UBound(JR_Values), 1) = JR_Values End With 'Debug.Print dVALs.Count dVALs.RemoveAll: Set dVALs = Nothing Debug.Print Timer End Sub 

该例程运行的时间(无辅助列)是45.72秒。 打破它,它只花了整整13.4秒来build立字典,剩下的大部分被实际的查找占用了半秒钟,在这里和那里归因于从工作表的值大批种子变种arrays。

Multi_Col_Match_Array_in_Memory

所以Scripting.Dictionary在这里是明显的赢家。 不幸的是,当值发生变化时,它不会自动计算各列中的更新,但在开发的这个阶段,工作表应设置为手动计算。 将基于公式的解决scheme之一设置为重新计算的单一重新计算值,似乎是时间的无效率花费。

总而言之,这是非常有意义的。 原始的数组公式类似于在两个字段上有一个INNER JOIN的SQL SELECT语句,如果我的SELECT语句运行效率低下,我会做的第一件事情就是查看表的索引。

在相关说明中,包含这么多数据的任何工作簿都应保存为Excel二进制工作簿,而不pipe它是否已启用macros。 二进制工作簿(.XLSB)的文件大小通常是相当于.XLSX或.XLSM的大小的1/3。 除了更快的初始加载时间,许多批量操作应该certificate更快。

任何想testing自己的优化的人都可以在这里find我的示例.XLSB工作簿。 不要盲目地运行这些程序,而不会先看到自己进入的内容。


¹ 数组公式需要使用Ctrl + Shift + Enter 来完成。 一旦正确input第一个单元格,就可以像任何其他公式一样向下或向右填充或复制它们。 尝试和减less您的全列引用范围更接近代表实际数据的范围。 数组公式将计算周期对数化,所以最好将参考范围缩小到最小。 有关更多信息,请参阅数组公式的示例 。