Here is an alternative solution that uses array-based formulas like MAP, BYROW, LAMBDA. Some users may not be able to use Apps Script (due to employer restrictions) or string-hacking methods (due to side effects on type conversion), so this solution would work for them. It also generalizes to multiple-column tables, i.e., it will combine multiple columns from the two tables.
Definitions. In your example, we'll assume Table1 is in a range on one sheet (TableA!A1:B3) and Table2 is in a range on another (TableB!A1:B5). The desired result will go into the range 'Result set'!A1:B6. (I edited your question to indicate the assumed ranges of each example table so I can reference them better; I chose range specifiers that are consistent with pre-existing answers.)
Formula. The formula below defines a lambda function with four parameters, and then invokes it using the following ranges fromm your exmaple tables as arguments.
| parameter | argument |
|---|---|
data_left |
Table1!A2:A4 |
keys_left |
Table1!A2:A4 |
data_right |
Table2!A2:A5 |
keys_right |
Table2!B2:B5 |
= LAMBDA(
data_left, keys_left, data_right, keys_right,
LET(
index_left, SEQUENCE( ROWS( keys_left ) ),
matches, MAP( index_left, keys_left, LAMBDA( id_left, key_left,
LET(
row_left, XLOOKUP( id_left, index_left, data_left ),
matches_right, IFERROR( FILTER( data_right, keys_right = key_left ), ),
TOROW( BYROW( matches_right, LAMBDA( row_right,
HSTACK( row_left, row_right )
) ) )
)
) ),
wrapped, WRAPROWS( FLATTEN(matches), COLUMNS(data_right) + COLUMNS(data_left) ),
notblank, FILTER( wrapped, NOT(ISBLANK(CHOOSECOLS(wrapped, 1))) ),
notblank
)
)( `Table1!A2:A4`, `Table1!A2:A4`, `Table2!A2:A5`, `Table2!B2:B5` )
How it works?
index_left: Create a temporary, primary-key array to index the left table, so you can retrieve rows from it later.matches: For each value in the index:
row_left: XLOOKUP the corresponding row from data_leftmatches_right: use FILTER to find all matching rows in the data_right; or NA() if there were nonematches_right (or NA() if no matches), concatenate it with a copy of row_leftTOROW() to flatten the resulting array into a single row, because MAP can return 1D array for each value of the index but not 2D array. (This makes a mess but we fix it later.)wrapped: The resulting array will have as many rows as the filtered index, but number of columns will vary depending on the maximum number of matches for any given key. Use WRAPROWS to properly stack and align matching rows. This leads to a bunch of empty blank rows but ...notblank: ... those are easy to filter out.Generalize. To apply this formula to other examples, just specify the desired ranges (or the results of ARRAYFORMULA or QUERY operations) as arguments; keys_left and keys_right must be single column but data_left and data_right can be multi-column and the resulting array will contain all columns of both. (If you expect to use this a lot, you could create a Named Function with the four parameters as "Argument placeholders", and with the body of the LAMBDA as the "Function definition".)
Named Function. If you just want to use this, you can import the Named Function LEFTJOIN from my spreadsheet functions. That version assumes the first row contains column headers. See documentation at this GitHub repo.. Screenshot below shows application of this named function in cell I1: ="LEFTJOIN( E:G, F:F, B:C, A:A)". Note that numeric-type data in the source tables remain same type in the result.