简体   繁体   English

Excel VBA-UDF包装VLOOKUP时的行为异常且性能不佳

[英]Excel VBA - strange behaviour and poor performance when UDF wraps VLOOKUP

I want to write a user defined function that wraps VLOOKUP. 我想编写一个包装VLOOKUP的用户定义函数。 All it requires is a reference to the column that data should be imported from, and it will execute a VLOOKUP assuming that the IDs are in column A and there are fewer than 3000 rows to search. 它所需要的只是对应从中导入数据的列的引用,并且将假定ID在列A中并且要搜索的行少于3000行,它将执行VLOOKUP。

Function AutoVlookup( importFrom As Range) As Variant
    Dim arg1, arg2, arg3, arg4 As Variant
    Dim arg1Str, arg2Str As String

    arg1Str = "$A" & Application.Caller.row 'get ID
    arg1 = Application.Caller.Parent.Range(arg1Str)
    arg2Str = "$A$1:$" & Split(cells(1, importFrom.column).Address, "$")(1) & "$3000"
    arg2 = importFrom.Parent.Range(arg2Str) 'get range to search in (in other workbook)
    arg3 = importFrom.column 'get column to return
    arg4 = False 'exact match

    AutoVlookup = Application.WorksheetFunction.VLookup(arg1, arg2, arg3, arg4)   
End Function

I am running into two problems. 我遇到两个问题。

Firstly, the execution time is terrible. 首先,执行时间很糟糕。 It takes several minutes to run this formula 1000 times, whereas the same VLOOKUP not wrapped in a UDF is very fast. 运行此公式1000次需要几分钟,而未包装在UDF中的相同VLOOKUP则非常快。

Secondly, when I first fill a column with =AutoVLookup(<column in other workbook>) every row will incorrectly show the same result until something triggers them to recalculate. 其次,当我第一次用=AutoVLookup(<column in other workbook>)填充一列时,每一行都会错误地显示相同的结果,直到某些东西触发它们重新计算。

What am I doing wrong? 我究竟做错了什么?


edit, answer: 编辑,回答:

Here is the code I made using advice from Santosh and Charles: 这是我根据Santosh和Charles的建议编写的代码:

Function EasyLookup(importFrom As Range) As Variant
    Application.Volatile False 'does not recalculate whenever cells on sheet change

    Dim Id As String
    Dim match As Integer
    Dim importColumnAddress As String
    Dim initialCalculationSetting As XlCalculation
    Dim initialScreenUpdateMode As Boolean
    Dim initialEnableEventsMode As Boolean

    'saving the settings, to be reverted later
    initialScreenUpdateMode = Application.ScreenUpdating
    initialCalculationSetting = Application.Calculation
    initialEnableEventsMode = Application.EnableEvents
    'changes screen update and calculation settings for performance
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    Application.EnableEvents = False

    'find ID on formula's sheet
    Id = Application.caller.Parent.Cells(Application.caller.row, 1).value
    'find row with ID on column A of data source sheet
    match = Application.WorksheetFunction.match(Id, importFrom.Parent.Range("$A$1:$A$4000"), 0) 'assumes no more than 4000 rows.

    'retrieve value from importFrom's column, on the row where ID was found
    importColumnAddress = Split(Cells(1, importFrom.column).Address, "$")(1)
    importColumnAddress = importColumnAddress & ":" & importColumnAddress
    EasyLookup = Application.WorksheetFunction.Index(importFrom.Parent.Range(importColumnAddress), match)

    'revert performance tweaks
    Application.ScreenUpdating = initialScreenUpdateMode
    Application.Calculation = initialCalculationSetting
    Application.EnableEvents = initialEnableEventsMode
End Function

It is much faster because it does not read in as much data, as it uses INDEX/MATCH rather than VLOOKUP. 它速度更快,因为它不读取太多数据,因为它使用INDEX / MATCH而不是VLOOKUP。 It also does not recalculate every time a cell in the sheet changes. 每次工作表中的单元格更改时,它也不会重新计算。

Try below code : 试试下面的代码:

Function AutoVlookup(importFrom As Range) As Variant

    Application.Volatile False
    Application.Calculation = xlCalculationManual
    Application.ScreenUpdating = False
    Application.EnableEvents = False

    Dim arg1, arg2, arg3, arg4 As Variant
    Dim arg1Str, arg2Str As String
    Dim rng As Object

    Set rng = Application.Caller
    arg1Str = "$A" & rng.Row    'get ID
    Set arg1 = Application.Caller.Parent.Range(arg1Str)

    arg2Str = "$A$1:$" & Split(Cells(1, importFrom.Column).Address, "$")(1) & "$3000"
    Set arg2 = importFrom.Parent.Range(arg2Str)    'get range to search in (in other workbook)

    arg3 = importFrom.Column    'get column to return
    arg4 = False    'exact match

    AutoVlookup = Application.VLookup(arg1, arg2, arg3, arg4)

    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
End Function

The main reasons your UDF is slow are: 您的UDF速度慢的主要原因是:
1) you are forcing it to import 3000 rows of data from Excel to the VBA variant and then passing the 3000 rows of data back to VLOOKUP rather than just using a reference to the range 1)您强迫它从Excel导入3000行数据到VBA变体,然后将3000行数据传回VLOOKUP,而不仅仅是使用对范围的引用
2) You are not bypassing the VBE Refresh bug 2)您没有绕过VBE Refresh错误

see the series of posts about building a faster lookup etc at 请参阅以下有关构建更快的查找的系列文章
http://fastexcel.wordpress.com/2011/07/20/developing-faster-lookups-part-1-using-excels-functions-efficiently/ http://fastexcel.wordpress.com/2011/07/20/developing-faster-lookups-part-1-using-excels-functions-efficiently/

Also your UDF will not work correctly in circumstances where it references cells that are not included in the importfrom range. 同样,您的UDF在引用importfrom范围中未包含的单元格的情况下将无法正常工作。

Finally I am not sure I understand what you are trying to achieve: would it not be simpler (and much more efficient) to use INDEX or implicit referencing rather than VLOOKUP? 最后,我不确定我是否理解您要实现的目标:使用INDEX或隐式引用而不是VLOOKUP会不会更简单(效率更高)?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM