【问题标题】:Spare time when searching rows between worksheets在工作表之间搜索行时的空闲时间
【发布时间】:2015-04-24 18:07:34
【问题描述】:

我写了一些代码,计算时间太长了。

它从工作表中“刷”特定列中的行(Plan1,有 11,617 行,从第二行开始数据),查找单元格的值,存储该值,在一秒钟内搜索特定列中的每一行工作表(Plan2,有 158,715 行,也从第 2 行开始数据)并验证遇到的值是否与搜索的值匹配。如果为 true,则存储该值,然后将其分配给上一个工作表 (Plan1) 中未使用的单元格,在同一行但在新列中。它可以工作,但由于行数很大,因此完成 Plan1 中的每一列大约需要 1 小时。

有一次,我尝试使用 VLOOKUP,它花费的时间非常少(大约 5 分钟),但是数据异常损坏,所以我开始使用 VBA 编程来获得更高的数据准确性。我抬头看了看this question,但我的问题比答案的解释太具体了。我翻译了代码以便更好地理解,所以如果您发现语法错误,请不要担心;这段代码在翻译之前就可以工作。

最后,这是我的代码。

Sub AddAddress()

    Dim Plan1, Plan2 As Worksheet
    Dim FirstRow As Long
    Dim LastRow As Long
    Dim CurrentRow As Long
    Dim CalcMode As Long
    Dim ViewMode As Long
    Dim SoughtId, EncounteredId, Address As String
    Dim SuccessCounter As Integer
    Dim StartTime, EndTime, ElapsedTime As Date

    StartTime = Time()

    Set Plan1 = Application.Worksheets("Plan1")
    Set Plan2 = Application.Worksheets("Plan2")

    'Define calculation mode
    With Application
        CalcMode = .Calculation
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With

    'Use Plan1
    With Plan1

        'Select this worksheet
        .Select

        'Memory optimization
        ViewMode = ActiveWindow.View
        ActiveWindow.View = xlNormalView
        .DisplayPageBreaks = False

        'First and last rows' loop
        FirstRow = .UsedRange.Cells(1).Row
        LastRow = .UsedRange.End(xlDown).Row

        'Loop execution
        For CurrentRow = LastRow To FirstRow Step -1

            'Check Id value in A column
            With .Cells(CurrentRow, "A")

                'Store SoughtId
                SoughtId = .Value

                'Search Address via Id on Plan2
                With Plan2

                    .Select
                    Dim ActiveCell As String

                    With .Range("D:D")

                        'Search Id
                        If (SoughtId = .Find(SoughtId)) Then

                            EncounteredId = SoughtId

                        End If

                        ActiveCell = .Find(SoughtId).Address

                    End With

                    'Define/store Address
                    With .Range(ActiveCell)

                        'Being in current column, go to the column that
                        'contains the wanted value if this value is not empty
                        If .Offset(0, 9).Value <> "" Then

                            Address = .Offset(0, 9).Value

                        End If

                    End With

                End With

                Plan1.Select

                'Append Address obtained value in corresponding row's cell
                'and increment SuccessCounter
                With .Offset(0, 15)

                    .Value = Address

                End With

            SuccessCounter = SuccessCounter + 1

            End With

        Next CurrentRow

    End With

    ActiveWindow.View = ViewMode
    With Application
        .ScreenUpdating = True
        .Calculation = CalcMode
    End With

    EndTime = Time()
    ElapsedTime = EndTime - StartTime

    MsgBox "Operation finished!" & vbNewLine & vbNewLine & "Added addresses: " & SuccessCounter & vbNewLine & "Time elapsed: " & ElapsedTime

End Sub

【问题讨论】:

  • 我不会在如此大的工作表 (Plan2) 上重复使用 Find(),而是使用字典查找。在此处查看我的示例:stackoverflow.com/questions/14265919/…
  • 您的数据实际上是从第 1 行开始,还是有列标题标签,而实际数据从第 2 行开始?
  • @TimWilliams 我现在正在评估你的答案,很快就会给你一个简报。
  • @Jeeped 我编辑了帖子来回答你的问题

标签: vba excel


【解决方案1】:

我会尽量不要使用.select 方法在两个工作表之间来回切换。而是尝试直接在工作表上引用这些值而不进行切换。它看起来像Worksheets("Plan1").Range("Range You Want")。也尽量不要使用ActiveCell,因为这会导致excel改变它已经选择了数十万次的单元格,这很慢。

希望这会有所帮助。

【讨论】:

  • 你的想法改进并清理了很多我的代码,但是 3 多个小时过去了,它仍然没有完成...... >
【解决方案2】:
  1. 您正确地使用了With ... End With 块,但通过在块中添加.Select 命令来破坏它们的目的。
  2. .Find 一次以查看是否存在某物,然后再次.Find 以获取值
  3. ActiveCellAddress等是保留字,不应重新定义为变量。
  4. Dim Plan1, Plan2 As Worksheet 之类的声明将 Plan1 创建为变体而非工作表类型。

看看这是否会加快速度。

Sub Addaddr()
    Dim Plan1 As Worksheet, Plan2 As Worksheet
    Dim rw As Long, FirstRow As Long, LastRow As Long
    Dim CalcMode As Long
    Dim SoughtId, addr As String
    Dim SuccessCounter As Long
    Dim StartTime As Date, EndTime As Date, ElapsedTime As Date

    StartTime = Time()

    Set Plan1 = ActiveWorkbook.Worksheets("Plan1")
    Set Plan2 = ActiveWorkbook.Worksheets("Plan2")

    'Define calculation mode
    With Application
        .ScreenUpdating = False
        CalcMode = .Calculation
        .Calculation = xlCalculationManual
        .EnableEvents = False
    End With

    With Plan1
        'First and last rows' loop
        FirstRow = 1
        LastRow = .Cells(Rows.Count, 1).End(xlUp).Row

        For rw = LastRow To FirstRow Step -1

            'Store SoughtId
            SoughtId = .Cells(rw, "A").Value

            'Search addr via Id on Plan2
            addr = vbNullString
            With Plan2
                If CBool(Application.CountIf(.Range("D:D"), SoughtId)) Then
                    addr = .Cells(Application.Match(SoughtId, .Range("D:D"), 0), "M").Value
                End If
            End With
            If CBool(Len(addr)) Then
                .Cells(rw, "O") = addr
                SuccessCounter = SuccessCounter + 1
            End If

        Next rw

    End With

    With Application
        .EnableEvents = True
        .Calculation = CalcMode
        .ScreenUpdating = True
    End With

    EndTime = Time()
    ElapsedTime = EndTime - StartTime

    MsgBox "Operation finished!" & vbNewLine & vbNewLine & "Added addres: " & SuccessCounter & vbNewLine & "Time elapsed: " & ElapsedTime

End Sub

我使用工作表COUNTIF function 确定是否存在交叉引用值,然后使用MATCH function 检索行号。这不是最有效的,但可能比没有错误控制的两个.Find 操作更好。

如前所述,这将真正受益于基于内存的处理,如字典或变量数组。看看定时测速here.

【讨论】:

  • 非常感谢你,因为我看到你在VBA编程方面经验丰富,你的代码更干净清晰。但对我不起作用。我修改了第 36 行和第 40 行,以通过变量获取 MO 列(例如 addr = .Cells(Application.Match(SoughtId, .Range("D:D"), 0), myVariable).Value,其中 myVariable 属于 String 类型,并且通过 InputBox 存储了 m)。结果是一个无限循环。我确实为一列运行了代码以完成信息,然后我去做了其他事情。花了大约两个小时,当我回来时,我看到代码还在运行。 ://
  • 另一件事,您所说的.Select 命令是能够更改窗口视图。在.Select 命令之后的ViewMode = ActiveWindow.ViewActiveWindow.View = xlNormalView 行对应于返回正常视图以提高速度的宏。我会从here 获取部分代码。无论如何,我现在再次更改了代码,遵循您的代码改进(如删除.Select 命令)并调整我需要的内容。它仍然很慢,但我想没有办法在不慢的情况下这样做......
【解决方案3】:

最快的方法仍然是使用带有 Excel 内置函数的公式。我将此公式放入工作表“Plan1”的 O 列:

=IF(ISERROR(MATCH($A1;Plan2!$D:$D;0));"";OFFSET(Plan2!$D$1;MATCH($A1;Plan2!$D:$D;0)-1;9))

它是这样工作的:

MATCH($A1;Plan2!$D:$D;0))
如果在 Plan2!$D:$D 范围内找到 Plan1!$A1 中的值,则返回行号,否则返回 #NV 错误。
如果未找到,则公式返回空字符串
如果找到,则返回相同行号但列偏移量为 9(“D”->“M”)的值。

这确实有效。我用长度为 600 的 Plan1 和长度为 15.0000 的 Plan2 对其进行了测试。在我的 PC 上重新计算大约需要 1 秒。 Jeedped 提供的 VBA 子程序确实需要大约相同的时间。

当然,重复 MATCH() 函数会消耗我们的运行时间。你放得更好
MATCH($A1;Plan2!$D:$D;0)
进入未使用的屏幕外列(例如“X”)并使用
=IF(ISERROR($X1);"";OFFSET(Plan2!$D$1;$X1-1;9))
在目标列中。 (示例公式都进入第 1 行)。

【讨论】:

  • Plan1 工作表中有 11K 行,我建议使用某种形式的非易失性 INDEX function 代替 OFFSET function。也许像'Plan1'中的=IFERROR(INDEX('Plan2'!M:M, MATCH(A1, 'Plan2'!D:D, 0)), "")!O:O.
  • 我故意使用 OFFSET 函数,因为 OP 想要比较 Plan1!A 值和 Plan2!D 值,但将 Plan2!I 值检索到 Plan1!O。查看原始代码。
  • 我通常不建议在整个工作簿中的任何值发生变化时重新计算任何公式的 11K 行,但这取决于 OP。无论如何,您可能想要更改偏移量的 cols 参数。 D列右侧的6列似乎是J列而不是M列。我相信正确的列偏移量是9
  • 你完全正确,在这两个方面。直到 OP。我已经更正了偏移量,谢谢。
  • @user1016274 谢谢你,你的公式确实对我有用,但只检索到第一行的引用值,这对我没有用,因为我需要用公式将单元格填充到最后一行(换句话说,当我填充它们时,填充的单元格与第一个单元格保持相同的值)。我需要验证Plan1 中的列中的单元格值是否与Plan 2 中遇到的单元格值匹配,然后在同一行中获取与Plan2 中遇到的单元格值相关的值,并将其放入Plan1 中对应单元格的值,在同一行但在另一个未使用的列(任何)中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-09
  • 2022-06-11
  • 1970-01-01
  • 1970-01-01
  • 2012-08-09
  • 1970-01-01
  • 2018-12-13
相关资源
最近更新 更多