带有Intersect的UDF运行缓慢

时间:2017-10-30 11:21:33

标签: excel vba performance excel-vba

所以我创建了一个函数来替换一些手动索引/匹配公式。请注意,此功能有效,但问题在于速度。所以我有一个6列和大约的数据透视表。 200.000行。我希望这能找到价值(并且我不使用pivotfunctions,这意味着这只是一个以数据透视格式表的表)我发现这比在常规数据表中运行更快。两者都将从SQL表中导入。

这个公式的一个部分立即运行,但是当我在同一张表中有几百个时,性能会变慢。

那么关于如何提高速度的想法呢?

Function getnum2(ByVal Comp As String, Period As String, Measure As String, Optional BU As String, _
    Optional Country As String, Optional Table As String, Optional TableSheet As String) As Double

Dim pTable As PivotTable, wTableSheet As Worksheet

If BU = "" Then
    BU = "Group"
End If
If Country = "" Then
    Country = "Total"
End If
If TableSheet = "" Then
    Set wTableSheet = Worksheets("Data")
Else
    Set wTableSheet = Worksheets(TableSheet)
End If
If Table = "" Then
    Set pTable = wTableSheet.PivotTables("PivotTable1")
Else
    Set pTable = wTableSheet.PivotTables(Table)
End If

'Find match
If Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
    pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
    pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
    pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
    pTable.PivotFields("Name").PivotItems(Measure).DataRange) Is Nothing Then
        getnum2 = "No match"
ElseIf Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
    pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
    pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
    pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
    pTable.PivotFields("Name").PivotItems(Measure).DataRange).Count > 1 Then
        getnum2 = "More than 1 match"
Else
    getnum2 = Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
    pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
    pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
    pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
    pTable.PivotFields("Name").PivotItems(Measure).DataRange)
End If

End Function

3 个答案:

答案 0 :(得分:1)

您可以使用变量:

,而不是将函数调用三次
Function getnum2(ByVal Comp As String, Period As String, Measure As String, Optional BU As String, _
    Optional Country As String, Optional Table As String, Optional TableSheet As String) As Double

Dim pTable As PivotTable, wTableSheet As Worksheet
Dim rgResult as Range

If BU = "" Then
    BU = "Group"
End If
If Country = "" Then
    Country = "Total"
End If
If TableSheet = "" Then
    Set wTableSheet = Worksheets("Data")
Else
    Set wTableSheet = Worksheets(TableSheet)
End If
If Table = "" Then
    Set pTable = wTableSheet.PivotTables("PivotTable1")
Else
    Set pTable = wTableSheet.PivotTables(Table)
End If

'Find match
Set rgResult = Intersect(pTable.PivotFields("Bank").PivotItems(Comp).DataRange.EntireRow, _
    pTable.PivotFields("Date").PivotItems(Period).DataRange.EntireRow, _
    pTable.PivotFields("Business Unit").PivotItems(BU).DataRange.EntireRow, _
    pTable.PivotFields("Country").PivotItems(Country).DataRange.EntireRow, _
    pTable.PivotFields("Name").PivotItems(Measure).DataRange)
if rgResult Is Nothing Then
        getnum2 = "No match"
ElseIf rgResult.Count > 1 Then
        getnum2 = "More than 1 match"
Else
    getnum2 = rgResult.Value
End If

End Function

答案 1 :(得分:0)

实现这一目标的一个非常简单的方法是使用两个数据透视表。

  • 在数据透视表1中,放置所有字段,但输入您想要的数字 返回ROWS区域,并输入要返回的字段 VALUES区域,聚合设置为COUNT。
  • 在数据透视表2中,放置所有字段,但输入您想要的数字字段 返回ROWS区域,并输入要返回的字段 VALUES区域的聚合设置为SUM或MIN或MAX(它没有 事情)。

然后你可以使用一个参数化的GETPIVOTDATA函数来检查数据透视表1以查看你正在查找的东西是否是唯一的(即COUNT = 1),如果是,那么查找该项目的SUM / MIN / MAX PivotTable2。鉴于该项是唯一的,则SUM / MIN / MAX仅在一个数字上运行,因此不执行任何操作。

以下是使用简化数据的外观:

enter image description here

我已经为两个Pivots添加了条件格式以突出显示我们想要返回文本“Multiple Items”的多个出现,正如您所看到的,填充Lookup表的第6列的公式只返回唯一根据您的要求项目。

这是公式,使用表格表示法,因为我的查找范围已变成Excel表格:

=IF(GETPIVOTDATA("6",$A$3,"1",[@1],"2",[@2],"3",[@3],"4",[@4],"5",[@5])=1,GETPIVOTDATA("6",$H$3,"1",[@1],"2",[@2],"3",[@3],"4",[@4],"5",[@5]),"Multiple Items")

如果我将Lookup表中的输入单元格随机化,您可以看到当某些项目不在数据透视表中时会发生什么:

enter image description here

此方法有效,因为您要返回的字段是数字字段,这意味着您可以将其添加到数据透视表的VALUES窗格中。但你仍然可以使用它来返回字符串,方法是在源数据中添加一个唯一的ID,例如行号,然后将其放在VALUES字段中,然后使用双GETPIVOTDATA查找检索它并使用它来检索关联的字符串在源数据中。

另一种方法是使用合适的分隔符(例如管道符号)将列连接到主键然后将其用作查找键。如果您对已排序的数据进行二进制搜索,则会很快。 (我在http://dailydoseofexcel.com/archives/2015/04/23/how-much-faster-is-the-double-vlookup-trick/讨论这个问题。不好的一面是,如果有多个项目,您将不会收到警告。但是可以使用第一个返回的匹配位置进行第二次查找,看看是否得到另一个结果,如果是,则返回“Multiple Items”。这将超级快。

答案 2 :(得分:0)

这是执行此操作的最快方法:在已排序的查找表上使用二进制匹配。 在左边,我有5列x 1048575行在1到10之间的随机数。这些行已在G列中连接成一个非唯一键,然后在该键上升序排序。

enter image description here

(因为连接键是文本,它从左到右按字母顺序排序,这就是1 | 1 | 1 | 1 | 10和1 | 1 | 1 | 1之间出现1 | 1 | 1 | 1 | 10的原因。 1 | 2)

我在G列中给出了Concat命名范围的数据,以简化公式。当且仅当该项对数据集是唯一的时,我在J2中的查找公式才返回查找项的行号。公式是:

=IF(OR( AND(INDEX(Concat,MATCH(I2,Concat,1))=I2, MATCH(I2,Concat,1)=1),  AND(INDEX(Concat,MATCH(I2,Concat,1))=I2,INDEX(Concat,MATCH(I2,Concat,1)-1)<>I2)),MATCH(I2,Concat,1),NA())

对于一个实例,对于1048576行的查找表,这将在0.01毫秒内执行。我上面的双GETPIVOTDATA方法需要6毫秒。所以你有它:一个复杂的公式,提高了600倍的效率。

我可以在以后需要解释这个公式,但请注意,一些复杂性是由于边缘情况,你可能在第1行出现一个唯一的项目。如果我省略了边缘情况,那么公式为如下:

=IF( AND(INDEX(Concat,MATCH(I3,Concat,1))=I3,INDEX(Concat,MATCH(I3,Concat,1)-1)<>I3),MATCH(I3,Concat,1),NA())