【问题标题】:Excel VBA correct/efficient formula populationExcel VBA 正确/高效的公式填充
【发布时间】:2017-03-22 18:19:19
【问题描述】:

下午好,

我在 VBA 中开发解决方案已有 6 年了。我一路上学到的很多东西都是从那个时候得到的,所以我终于有机会说谢谢!

今天我得出的结论是,我需要发布一个困扰我多年的问题。所以 - 我在 VBA for Excel 中最常做的一件事是使用应用于相关范围的公式的结果填充定义的范围。到目前为止,我已经使用以下方法来完成此操作:

Sub CalculateField()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
'''''''''''''''''''''''''''''''''
Dim RecordStop As Long

Set MyRange = ActiveSheet.Range("A:A")
RecordStop = Application.WorksheetFunction.CountA(MyRange)

'Apply Formula
Range("M2:M" & RecordStop).FormulaR1C1 = "=IF(RC[-7]=1,IF(ISERROR(VLOOKUP(CONCATENATE(RC[-2],""|"",1),R1C12:R[-1]C[-1],1,FA  LSE)),0,1),0)"

'Drop Formula, keep values
Range("M2:M" & RecordStop).Value = Range("M2:M" & RecordStop).Value

''''''''''''''''''''''''''''''''''
Application.ScreenUpdating = True
Application.DisplayAlerts = True

End Sub

我发现,虽然这种方法有效,但对于更大的文件/更复杂的公式,我在性能上的表现是巨大的!现在,我知道必须有一种更有效的方法来导出公式的结果并将它们直接注入希望结果作为值返回的工作表中。在上述方法中,我首先计算结果,然后用“值”(结果)覆盖“公式”。我目前正在处理一个需要 21 个计算字段并且平均在 100k 到 400k 记录之间的文件。

我担心现在是时候让我学习一种更好的方法,更专业的方法来做到这一点。我在这里和强大的谷歌搜索了如何做到这一点的例子。我在 SO 上找到了一些帖子,描述了我认为是解决方案的一部分,但是很难找到一个我可以遵循的具体简单示例来实施。我认为正确的方法涉及以下一项或全部:

  1. 数组
  2. 字典
  3. 收藏

...我希望你们能指出我正确的方向。一旦解决了这个问题,我觉得这将大大提升我在 VBA 开发中的水平。由于我是自学成才,我一直犹豫是否要深入研究,并且从未真正想过我会做到这一点,因为这不是我的主要角色,但现在很想克服这个问题,因此我非常感谢您的帮助。

顺便说一句;我很擅长自学,所以如果碰巧已经有一个帖子可以让我到达那里,那就给我指明方向。同样,在搜索中我找不到我要找的东西,所以如果这是因为不知道如何(描述的词)寻找答案的结果,我深表歉意。

【问题讨论】:

  • 这是一个很好的问题,但我不确定会有一个很好的答案。很大程度上取决于需要评估的具体公式,我怀疑是否有人能够提出一个万能的解决方案。我担心这个问题会因为“太宽泛”而被关闭。 :(
  • 循环遍历范围并用结果值填充数组,然后将数组转储到范围(一举)几乎肯定会更快加载,因为没有依赖项会重新计算(注意,您已禁用 ScreenUpdatingDisplayAlerts,但未设置 Calculation = xlCalculationManual,您可以尝试在退出子程序之前记住恢复为 Application.Calculation = xlCalculationAutomatic
  • 特定于您的示例,很难推测出正确的方法,但我认为首先是将所有源放入数组中,操作内存中的数组,然后一步编写最终的数组到目标表。我还将实现一个 Application.EnableEvents = False。最后添加它,但可能只会显着提高您的性能而无需做任何其他事情。此外,在关闭计算的情况下,您可以在写入值之前仅针对 Range("M2:M" & RecordStop) 进行计算。
  • 因此,如果您可以禁用临时计算,那么也许这将为您提供足够的性能提升。值得一试。否则,您可以尝试将该公式转换为纯 VBA,这也可能会有所帮助,但您必须进行性能测试才能真正确定。
  • @YowE3K - 感谢您更正我原来的帖子

标签: excel vba


【解决方案1】:

我可能会先从这个开始,然后再修补一堆。

如果您没有获得性能提升,那么我会考虑将您的源数据提取到数组中,在代码中修改它们,然后将您的结果写在一行代码中写回您的单元格。

这里有一个很好的使用 Array 路由的链接:VBA Arrays And Worksheet Ranges

Dim PrevCalc As XlCalculation
With Application
    PrevCalc = .Calculation
    .Calculation = xlCalculationManual
    .Cursor = xlWait
    .Calculate
    .EnableEvents = False
    .ScreenUpdating = False
End With
Dim RecordStop As Long

Set MyRange = ActiveSheet.Range("A:A")
RecordStop = Application.WorksheetFunction.CountA(MyRange)

'Apply Formula
With Range("M2:M" & RecordStop)
    .FormulaR1C1 = "=IF(RC[-7]=1,IF(ISERROR(VLOOKUP(CONCATENATE(RC[-2],""|"",1),R1C12:R[-1]C[-1],1,FA  LSE)),0,1),0)"

    'Calc only the target
    .Calculate

    'Drop Formula, keep values
    .Value = .Value
End With 'Range("M2:M" & RecordStop)

With Application
    .Cursor = xlDefault
    .Calculate
    .Calculation = PrevCalc
    '.ScreenUpdating = True 'Not Needed...
    .EnableEvents = True
End With

【讨论】:

  • 太棒了,谢谢,我将把这个方案合并到所有子程序的顶部。我很惊讶这个肠道运行时间减少了 20%(总运行时间仍然 > 2.5 小时)。与我想要的性能还有很大差距,但至少现在我有了这个,并且确定是时候学习“内存中的数组”了。
  • 使用“with”子句可能会遇到减速带,但不确定。无论如何,我已将其添加到解决方案中。
  • 一个充满可能性的全新世界!谢谢大家,我认为上述标准开始/结束协议和数组的组合是解决方案。现在来研究“如何”。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2012-11-22
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 2016-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多