简体   繁体   English

VLOOKUP() 替代使用数组

[英]VLOOKUP() Alternative using Arrays

I've been experimenting with arrays to find a faster alternative to VLOOKUP(), which can take a long time to execute with very large data sets.我一直在尝试使用数组来找到 VLOOKUP() 的更快替代方法,它可能需要很长时间才能执行非常大的数据集。

I searched SO and many other sites, grabbing snippets of code.我搜索了 SO 和许多其他网站,抓取了代码片段。

The data:数据:

  • A1:A5 the list of values to lookup (1,2,3,4,5) A1:A5 要查找的值列表 (1,2,3,4,5)
  • C1:C5 the range to 'find' the values (2,4,6,8,10) C1:C5 “查找”值的范围 (2,4,6,8,10)
  • D1:D5 the range of values to 'return' (a,b,c,d,e) D1:D5 要“返回”的值范围 (a,b,c,d,e)

在此处输入图片说明

B1:B5 is where I'd like to paste the 'looked-up' values. B1:B5 是我想粘贴“查找”值的地方。

The code works up to a point, in that it does return correct values for the 'looked-up' value's position in C1:C5 – and the correct values in the adjacent cells in D1:D5.该代码在某种程度上起作用,因为它确实返回了 C1:C5 中“查找”值位置的正确值——以及 D1:D5 中相邻单元格中的正确值。

When I try to load the returned values to Arr4 (the array to be pasted back to the sheet) which is saying <Type mismatch> when I hover the mouse over it.当我尝试将返回的值加载到Arr4 (要粘贴回工作表的数组)时,当我将鼠标悬停在它上面时,它说<Type mismatch> It doesn't stop the code from executing, but it doesn't paste anything.它不会阻止代码执行,但不会粘贴任何内容。

My questions are:我的问题是:

  1. How do I populate the array Arr4 with the myVal2 values, and如何使用myVal2值填充数组Arr4 ,以及
  2. How do I paste it back to the sheet?如何将其粘贴回工作表?
Option Explicit
Sub testArray()
    Dim ArrLookupValues As Variant
    ArrLookupValues = Sheet1.Range("A1:A5")    'The Lookup Values
        
    Dim ArrLookupRange As Variant
    ArrLookupRange = Sheet1.Range("C1:C5")    'The Range to find the Value
        
    Dim ArrReturnValues As Variant
    ArrReturnValues = Sheet1.Range("D1:D5")    'The adjacent Range to return the Lookup Value
    
    Dim ArrOutput As Variant 'output array
        
    Dim UpperElement As Long
    UpperElement = UBound(ArrLookupValues)     'Used purely for the ReDim statement
        
    Dim i As Long
    For i = LBound(ArrLookupValues) To UBound(ArrLookupValues)
        Dim myVal As Variant
        myVal = ArrLookupValues(i, 1)
            
        Dim pos As Variant 'variant becaus it can return an error
        pos = Application.Match(myVal, ArrLookupRange, 0) 'pos always returns the correct position
            
        Dim myVal2 As Variant
        If Not IsError(pos) Then
            myVal2 = ArrReturnValues(pos, 1)           'myVal2 always returns the correct value
            ReDim Preserve ArrOutput(1 To UpperElement, 1 To 1)
            ArrOutput(i, 1) = myVal2
        Else
            ReDim Preserve ArrOutput(1 To UpperElement, 1 To 1)
            myVal2 = "Not Found"
            ArrOutput(i, 1) = myVal2
        End If
    Next i
        
    Dim Destination As Range
    Set Destination = Range("B1")
    Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = 

    ArrOutput
End Sub

According to @TM 's answer, you can even do that without looping just by using VLookup instead of Match :根据@TM 的回答,您甚至可以通过使用VLookup而不是Match来做到这一点而无需循环:

Public Sub testArraya()
    With Sheet1
        Dim ArrLookupValues() As Variant
        ArrLookupValues = .Range("A1:A5").Value        ' lookup values        1,2,3,4,5,6
    
        Dim ArrLookupReturnRange() As Variant          ' lookup range items   2,4,6,8,10
        ArrLookupReturnRange = .Range("C1:D5").Value   ' And return column D  a,b,c,d,e
    End With
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    '[1] Match all values at once and return other values of column D
    '    (found position indices or Error 2042 if not found)
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Dim ArrOutput() As Variant
    ArrOutput = Application.VLookup(ArrLookupValues, ArrLookupReturnRange, 2, 0)
    
    '[3] write results to any wanted target
    Dim Destination As Range
    Set Destination = Sheet1.Range("B1")         ' ‹‹ change to your needs
    Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = ArrOutput     
End Sub

Or even extremly shortened and almost a one liner:或者甚至极度缩短,几乎是一个班轮:

Public Sub testArrayShort()
    Const nRows As Long = 5 'amount of rows
    
    With Sheet1
        .Range("B1").Resize(nRows).Value = Application.VLookup(.Range("A1").Resize(nRows).Value, .Range("C1:D1").Resize(nRows).Value, 2, 0)
    End With
End Sub
  • Use proper error handling and an If statement instead of On Error Resume Next .使用正确的错误处理和If语句而不是On Error Resume Next

  • Also your Arr4 needs to be 2 dimensional like your other arrays.此外,您的Arr4需要像其他数组一样是二维的。 Even if it is only one column it needs no be Arr4(1 To UpperElement, 1 To 1) and Arr4(i, 1) = myVal2 .即使它只有一列,也不需要Arr4(1 To UpperElement, 1 To 1)Arr4(i, 1) = myVal2 Ranges are always 2 dimensional (row, column) even if there is only one column.即使只有一列,范围也始终是二维的(行、列)。

And I highly recommend to rename your array variables.我强烈建议重命名您的数组变量。 When ever you feel like you have to give your variable numbers, you can be sure you are doing it wrong.当您觉得必须提供可变数字时,您可以确定自己做错了。

Rename them like following for example:例如,将它们重命名如下:

  • Arr1 --› ArrLookupValues Arr1 --› ArrLookupValues
  • Arr2 --› ArrLookupRange Arr2 --› ArrLookupRange
  • Arr3 --› ArrReturnValues Arr3 --› ArrReturnValues
  • Arr4 --› ArrOutput Arr4 --› ArrOutput

This is only a simple modification but your code will extremely gain in human readability and maintainability.这只是一个简单的修改,但您的代码将极大地提高人类的可读性和可维护性。 You even don't need comments to describe the arrays because their names are self descriptive now.您甚至不需要注释来描述数组,因为它们的名称现在是自描述的。

Finally your output array can be declared the same size as the input arrays.最后,您的输出数组可以声明为与输入数组相同的大小。 Using ReDim Preserve makes your code slow, so avoid using it.使用ReDim Preserve会使您的代码变慢,因此请避免使用它。

So something like this should work:所以这样的事情应该有效:

Option Explicit

Public Sub testArray()
    Dim ArrLookupValues() As Variant
    ArrLookupValues = Sheet1.Range("A1:A5").Value
    
    Dim ArrLookupRange() As Variant
    ArrLookupRange = Sheet1.Range("C1:C5").Value
    
    Dim ArrReturnValues() As Variant
    ArrReturnValues = Sheet1.Range("D1:D5").Value

    Dim UpperElement As Long
    UpperElement = UBound(ArrLookupValues, 1)   
    
    'create an empty array (same row count as ArrLookupValues)
    ReDim ArrOutput(1 To UpperElement, 1 To 1)
    
    Dim i As Long
    For i = LBound(ArrLookupValues, 1) To UBound(ArrLookupValues, 1)
        Dim FoundAt As Variant 'variant because it can return an error
        FoundAt = Application.Match(ArrLookupValues(i, 1), ArrLookupRange, 0) 'pos always returns the correct position

        If Not IsError(FoundAt) Then
            ArrOutput(i, 1) = ArrReturnValues(FoundAt, 1)
        Else
            ArrOutput(i, 1) = "Not Found"
        End If
    Next i
    
    Dim Destination As Range
    Set Destination = Range("B1") 'make sure to specify a sheet for that range!
    Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub

Just for fun a slight modification of @PEH 's valid approach demonstrating a rather unknown way to excecute a single Match checking both arrays instead of repeated matches:只是为了好玩,对 @PEH 的有效方法稍作修改,展示了一种相当未知的方法来执行单个Match检查两个数组而不是重复匹配:

Public Sub testArray()
    With Sheet1
        Dim ArrLookupValues As Variant
        ArrLookupValues = .Range("A1:A5").Value             ' lookup values      1,2,3,4,5,6
    
        Dim ArrLookupRange As Variant                       ' lookup range items 2,4,6,8,10
        ArrLookupRange = .Range("C1:C5").Value
    
        Dim ArrReturnValues As Variant                      ' return column D    a,b,c,d,e
        ArrReturnValues = .Range("D1:D5").Value
    End With
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    '[1] Match all item indices within ArrLookupRange at once 
    '    (found position indices or Error 2042 if not found)
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Dim ArrOutput
    ArrOutput = Application.Match(ArrLookupValues, ArrLookupRange, 0)
    
    '[2] change indices by return values
    Dim i As Long
    For i = 1 To UBound(ArrOutput)
        If Not IsError(ArrOutput(i, 1)) Then
            ArrOutput(i, 1) = ArrReturnValues(ArrOutput(i, 1), 1)
'        Else
'            ArrOutput(i, 1) = "Not Found"       ' optional Not Found statement instead of #NV
        End If
    Next i

    '[3] write results to any wanted target
    Dim Destination As Range
    Set Destination = Sheet1.Range("B1")         '<< change to your needs
    Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub

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

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