【问题标题】:Index Lookup Across Sheets; Slow Speeds (Excel VBA)跨工作表的索引查找;慢速(Excel VBA)
【发布时间】:2019-09-08 23:13:35
【问题描述】:

[这更像是一个优化问题,而不是代码问题;有更好的地方发这个吗?]

我试图(用最简单的术语)使用 3 列中的宏来索引一个值,索引引用另一个工作表(同一个工作簿)中的表。如果找到值,则输出结果;如果不是,则输出“NOT FOUND”。

我的问题是,在大约 1000 行代码中,它已经需要大约 7 秒才能运行。我之前将代码放在每列的 3 个单独循环中,但将其缩减为一个循环以帮助加快速度,但它仍然很慢。作为参考,我必须在 200K 到 900K 的任何地方运行此代码,具体取决于月份。

我在更繁重的代码中使用了数组,而且这些代码几乎不需要几秒钟,所以我觉得这是我想念/忘记的简单事情;我还听说 VBA 中的工作表函数未优化,可能是这样(以前从未在 VBA 中使用过索引),但这可能是错误信息。

注意: 我尽可能地缩短了代码,以便更容易找到核心问题;代码本身 100% 正确运行,只是速度很慢。我已将声明的变量及其类型放入 cmets 中,并且仅显示了 1 列的查找过程(其他两列的查找基本相同)。

Sub RefreshData()

Call TurnEverythingOff 'Turns off screenupdating, calculations, events, etc.
On Error GoTo Skip

'Variables that are declared (Dim'd) [Put into comments to save space]
    'SR_Data is the start row of data [Long]
    'ER_Data is the end row of data [Long]
    'SC_Data is the start col of data [String]
    'EC_Data is the end col of data [String]
    'Range/Array_Data are the respective range/array of the above 4 values [Set Range/Variant]
    '*****_Col is the numerical value representing the columns for lookup etc. [Long]
    '*****_Table is the range that the INDEX/Lookup will view (but in another sheet) [Range]
    'Array****Value is to hold the cell string [String]
    'Section_DNE is for when values aren't found in INDEX/Lookup [String]

'[MAPPING] Actually performs the lookup/logic
With Data 'Made the sheet codename this in VBA editor
.Activate
    ER_Data = .Cells(.Rows.Count, 1).End(xlUp).Row

    Set Range_Data = Range(SC_Data & SR_Data & ":" & EC_Data & ER_Data)
        Array_Data = Range_Data.Value





'Looking for assistance in speeding **THIS** section up.
    For Each DataCell In Range_Data.Columns(1).Cells
            'Debug.Print (DataCell.Address)
            With Application.WorksheetFunction

                'Defines the value in the cell that will need to be looked for
                ArrayDisciplineValue = Array_Data(DataCell.Row - SR_Data + 1, Discipline_Col)
                ArrayFundNameValue = Array_Data(DataCell.Row - SR_Data + 1, FundName_Col)                

            Mapping.Activate 'Sheet Codename, sheet contains tables for lookup

            'For Discipline Lookup
                On Error GoTo ErrorHandle1
                ArrayDisciplineValue = _
                    .Index(Discipline_Table, _
                    .Match(ArrayFundNameValue, Discipline_Table.Columns(1), 0), _
                    2)

            Data.Activate 'Sheet Codename

                'For Discipline Mapping
                Array_Data(DataCell.Row - SR_Data + 1, Discipline_Col) = ArrayDisciplineValue

            End With
    Next DataCell
End With







Range_Data.Value = Array_Data

ErrorHandle1:
'For when the INDEX LOOKUP fails to find the value...
ArrayDisciplineValue = Section_DNE
Resume Next

Skip:
Call TurnEverythingOn
End Sub

预期结果:1000 行需要大约 1 秒(因为它在数组中,它应该更快,不是吗?)

实际结果:1000 行大约需要 7 秒

将其扩展到 200,000 行。

【问题讨论】:

  • 注释掉 Mapping.ActivateData.Activate 注意索引/匹配的工作速度要快得多当数据在工作表上时 - 在这种情况下性能会更差 对数组执行查找。当您对工作表进行大量读取/写入时,使用数组是答案,因为它允许您批量处理这些操作:它不会加速所有操作。更多信息请看这里:stackoverflow.com/questions/7031416/…
  • 那么理想的情况是我将映射表移动到与数据相同的工作表(如果我想保持我的代码相同)吗?有什么方法可以让我的代码保持快速并将映射表放在不同的工作表上;从您提供的链接中, application.match/index 方法不起作用,因为它要慢得多,对吗?即,没有办法有效地跨工作表进行索引/匹配,应该改用 Do While 吗?
  • 无需移动映射表 - 您可以毫无问题地跨工作表执行查找。由于您不提供 SC_Data/EC_Data 的值,因此很难准确判断代码中发生了什么
  • 嗨蒂姆,我注释掉了 Mapping.Activate 和 Data.Activate;我以前在没有这些的情况下尝试过它,并且由于某种原因它一直在引用活动工作表上的范围,所以我之前一定有不同的东西导致了这种情况。如果有帮助,我可以发布 SC_Data/EC_Data 等值,但仅删除 .activate 部分就已经大大加快了速度!谢谢:)

标签: vba optimization


【解决方案1】:

是的,使用数组应该可以加快您的代码速度 - 前提是您不继续与单元格/工作表交互 - 并在内存(数组所在的位置)中执行所有操作(或尽可能多的工作)被存储)。

您将值读入这一行的数组中(这很好):

Array_Data = Range_Data.Value

然后你继续与工作表交互:

For Each DataCell In Range_Data.Columns(1).Cells

您这样做的原因似乎是您可以访问DataCellRow 成员——您反过来用它来引用数组中某个特定索引(或行)处的元素。

我认为您应该改用 LboundUbound 函数来遍历您的数组。这些函数是为数组设计的,使用它们意味着您不再需要与电子表格交互(访问成员/单元格)。

我无法完全重写您的代码,但希望像下面这样的内容可以让您更接近您想要实现的目标(在性能/速度方面)。请参阅 cmets 了解更多详情。另外,我建议您将Option Explicit 放在所有模块的顶部(如果您还没有的话)。

最后一点是您的代码包含一个名为Discipline_Table 的变量。您访问它的Columns 成员,所以我认为它是range(或其他东西)。理想情况下,您希望将其值读入数组。也许将其第一列读入一个数组,并将整个范围读入另一个数组(您似乎没有更改它们的值,因此在您的情况下,在循环之前读取它们一次应该没问题)。这就是我在下面所做的。否则,您仍在与循环内的一些工作表/单元格进行交互(您希望避免,因为它相对较慢)。

Option Explicit

Sub RefreshData()

    'Call TurnEverythingOff 'Turns off screenupdating, calculations, events, etc.
    'On Error GoTo Skip

    'Variables that are declared (Dim'd) [Put into comments to save space]
    'SR_Data is the start row of data [Long]
    'ER_Data is the end row of data [Long]
    'SC_Data is the start col of data [String]
    'EC_Data is the end col of data [String]
    'Range/Array_Data are the respective range/array of the above 4 values [Set Range/Variant]
    '*****_Col is the numerical value representing the columns for lookup etc. [Long]
    '*****_Table is the range that the INDEX/Lookup will view (but in another sheet) [Range]
    'Array****Value is to hold the cell string [String]
    'Section_DNE is for when values aren't found in INDEX/Lookup [String]

    '[MAPPING] Actually performs the lookup/logic
    With Data 'Made the sheet codename this in VBA editor
    '.Activate
        Dim SC_Data As String
        Dim EC_Data As String

        Dim SR_Data As Long

        Dim ER_Data As Long
        ER_Data = .Cells(.Rows.Count, 1).End(xlUp).Row

        Dim Range_Data As Range
        ' I added a . before your Range. Presume you wanted it to relate to your Data sheet
        ' rather than an implicit reference to ActiveSheet
        Set Range_Data = Range(SC_Data & SR_Data & ":" & EC_Data & ER_Data)

        Dim Array_Data() As Variant
        Array_Data = Range_Data.Value
    End With

    Dim firstColumnOfDisciplineTable() As Variant
    firstColumnOfDisciplineTable = Discipline_Table.Columns(1).Value

    Dim disciplineTable() As Variant
    disciplineTable = Discipline_Table.Value2

    Dim rowIndex As Long

    Dim Discipline_Col As Long
    Dim FundName_col As Long



    ' 1 here (below) refers to the first dimension - AKA "rows".
    ' If we passed 2 as the second argument, we would get the first and last "columns" of our array
    For rowIndex = LBound(Array_Data, 1) To UBound(Array_Data, 1)

        Dim ArrayDisciplineValue As String
        ArrayDisciplineValue = Array_Data(rowIndex - SR_Data + 1, Discipline_Col)

        Dim ArrayFundNameValue As String
        ArrayFundNameValue = Array_Data(rowIndex - SR_Data + 1, FundName_col)

        ' Use Application.Match (instead of Application.WorksheetFunction.Match)
        ' so that if you do get an error value, you can safely assign it to a variable of type Variant
        ' without getting an error at runtime.
        ' This saves you having to use On Error GoTo ... handling.
        Dim matchResult As Variant
        matchResult = Application.Match(ArrayFundNameValue, firstColumnOfDisciplineTable, 0)

        If IsNumeric(matchResult) Then
            ArrayDisciplineValue = Application.Index(disciplineTable, matchResult, 2)
        Else
            Dim Section_DNE As String
            ArrayDisciplineValue = Section_DNE
        End If

        'For Discipline Mapping
        Array_Data(rowIndex - SR_Data + 1, Discipline_Col) = ArrayDisciplineValue

    Next rowIndex

    Range_Data.Value = Array_Data


'Skip:
'Call TurnEverythingOn
End Sub

  • 我声明了disciplineTable() As Variant,因为我们正在从电子表格中读取值;其类型理论上可以是任何东西(字符串、数字、错误等)。 () 意味着我们只能为数组分配一个多单元区域/范围(而不是单个单元)——这很好,因为其余代码需要一个数组。

  • 在大多数情况下,Range.Value2Range.Value 之间没有任何区别。如果您愿意,您可能可以使用Range.Value(或者在这种情况下甚至只使用Range,它会隐式调用.Value)——除非您正在处理货币和日期并希望阻止它们被转换为@987654339 @s.

  • 不知道为什么 LboundUbound 不适合你,但似乎你有一个解决方法来处理你的开始行和结束行。请注意从范围读取的数组是从 1 开始的,因此您应该确保您指定的索引也是从 1 开始的。索引也需要相对于数组。

【讨论】:

  • 我什至没有考虑将表格读入数组;这是一个很好的建议,谢谢!我将尝试根据您的建议编辑我的代码,但有一个问题;为什么你有学科表()作为变体,什么是.Value2而不是.Value?我似乎不明白该部分的用途。此外,我尝试重新执行代码,并且使用 LBound 和 UBound 我在 RowIndex = 1 的地方卡住了,然后循环停止(但我可以使用 SR_Data 和 ER_Data 来获得相同的效果,所以很好!)。跨度>
  • 根据您的建议,我已将其缩短至约 15 秒,非常感谢 :)
  • @DoogieB,抱歉回复晚了,已编辑我的答案以回复您的问题。
【解决方案2】:

这里我实例化了一个对象,它将获取所有单元格 这样你优化,当你运行一个循环调用一个属性/方法 每次循环成为增益时,vba 都会调用该属性和方法 当你设置对象或值时,循环变得更快!

    Dim Allcells As Object
    Set Allcells = Range_Data.Columns(1).Cells
'Looking for assistance in speeding **THIS** section up.
    For Each DataCell In Allcells

希望你喜欢这个小费!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-27
    • 2010-12-23
    相关资源
    最近更新 更多