Excel combine Vlookups

2019-07-25 16:24发布

问题:

I have two files one is a Project Register that holds key information on a project and the other is a Risk log.

There is a 1:m relationship between entries in the Register and the Risk log. What I need to do is combine all of a project risks into one cell inside the project register file.

The matching field in both files is the Project ID field

Is there a way I can do this using a vlookup variant or multiple nested vlookups?

回答1:

Here's the user-defined function approach I mentioned (adapted from a different VLOOKUP-variant I already had made):

' Acts like VLOOKUP in a 1-to-many scenario by concatenating all values in matching rows
' instead of just returning the first match
Public Function VLOOKUP_MANY(lookup_value As String, lookup_range As Range, column_number As Integer, Optional delimiter As Variant) As Variant
    Dim vArr As Variant
    Dim i As Long
    Dim found As Boolean: found = False

    ' Set default delimiter
    If IsMissing(delimiter) Then delimiter = ", "

    ' Get values
    vArr = lookup_range.Value2

    ' If column_number is outside of the specified range, return #REF
    If column_number < LBound(vArr, 2) Or column_number > UBound(vArr, 2) Then
        VLOOKUP_MANY = CVErr(xlErrRef)
        Exit Function
    End If

    ' Search for matches and build a concatenated list
    VLOOKUP_MANY = ""
    For i = 1 To UBound(vArr, 1)
        If UCase(vArr(i, 1)) = UCase(lookup_value) Then
            VLOOKUP_MANY = VLOOKUP_MANY & delimiter & vArr(i, column_number)
            found = True ' Mark at least 1 result
        End If
    Next

    If found Then
        VLOOKUP_MANY = Right(VLOOKUP_MANY, Len(VLOOKUP_MANY) - Len(delimiter)) ' Remove first delimiter
    Else
        VLOOKUP_MANY = CVErr(xlErrNA) ' If no matches found, return #N/A
    End If
End Function

This will search the first column in the specified range for the specified value (same as VLOOKUP), but returns the values in the specified column number concatenated. It will return #N/A when no matches are found, and #REF if an invalid value is specified for the column number (e.g. you choose column 5 but only had a 4-column table).

In case you don't know about user-defined functions - you can just copy this VBA code into the VBE for a module in your workbook. Hit Alt+F11, go to Insert > Module at the top of the screen, then paste this code into the blank file that opens up. When you go to save, you'll have to save your workbook as Macro-Enabled (.xlsm) to keep the code working - Excel will remind you about this in the save screen.

Be forewarned: it's going to be slower than VLOOKUP as a result of having to look through the entire lookup range instead of being able to stop at the first match it finds.

If you're open to using an array formula instead, there are ways to speed up this sort of functionality for very large datasets...


Different version that leverages some of the benefits of array formulas to store lookup values and speedup subsequent calls:

' Acts like VLOOKUP in a 1-to-many scenario by concatenating all values in matching rows
'  instead of just returning the first match
' Utilizes a dictionary to speedup multiple matches (great for array formulas)
Public Function VLOOKUP_MANY_ARRAY(lookup_values As Range, lookup_range As Range, column_number As Integer, Optional delimiter As Variant) As Variant
    Dim vHaystack As Variant, vNeedles As Variant
    Dim i As Long
    Dim found As Boolean: found = False
    Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")

    ' Set default delimiter
    If IsMissing(delimiter) Then delimiter = ", "

    ' Get values
    vHaystack = lookup_range
    vNeedles = lookup_values

    ' If column_number is outside of the specified range, return #REF
    If column_number < LBound(vHaystack, 2) Or column_number > UBound(vHaystack, 2) Then
        VLOOKUP_MANY_ARRAY = CVErr(xlErrRef)
        Exit Function
    End If

    ' Add values to a lookup dictionary
    For i = 1 To UBound(vHaystack, 1)
        If dict.Exists(UCase(vHaystack(i, 1))) Then
            dict.Item(UCase(vHaystack(i, 1))) = dict.Item(UCase(vHaystack(i, 1))) & delimiter & vHaystack(i, column_number)
        Else
            dict.Add UCase(vHaystack(i, 1)), vHaystack(i, column_number)
        End If
    Next

    Dim outArr As Variant
    If IsArray(vNeedles) Then ' Check number of lookup cells
        ' Build output array
        ReDim outArr(1 To UBound(vNeedles, 1), 1 To 1) As Variant

        For i = 1 To UBound(vNeedles, 1)
            If dict.Exists(UCase(vNeedles(i, 1))) Then
                outArr(i, 1) = dict.Item(UCase(vNeedles(i, 1)))
            Else
                outArr(i, 1) = CVErr(xlErrNA)
            End If
        Next
    Else
        ' Single output value
        If dict.Exists(UCase(vNeedles)) Then
            outArr = dict.Item(UCase(vNeedles))
        Else
            outArr = CVErr(xlErrNA)
        End If
    End If

    VLOOKUP_MANY_ARRAY = outArr
End Function

This creates a Dictionary, which is a special structure that's really good for looking up values. There's a little extra overhead involved in building it, but once you have the structure, you can do lookups into it very quickly. This is especially nice with array formulas, which is basically when the exact same formula gets put into a whole collection of cells, then the function executes once and returns values for every cell (instead of just executing once, separately, for a bunch of cells). Enter it like an array formula with CTRL+SHIFT+ENTER, and make the first argument refer to all your lookup values instead of just one.

It will work without being used as an array formula, but it will be somewhat slower than the first function in that situation. However, if you use it in an array formula, you'll see huge speedups.



回答2:

RE-EDIT:

You might need to write a user defined function or write a macro (code on same link)