Often you will need to look up data in a table – and two functions most modellers are very familiar with are VLOOKUP and HLOOKUP, even in these new halcyon days of XLOOKUP – but let’s discuss that function next time. For now, do you realise it’s very easy to make a mistake with these functions?

Let’s start with a refresher.

**VLOOKUP(lookup_value, table_array, column_index_number, [range_lookup]) **has the following syntax:

**lookup_value:**what value do you want to look up?**table_array:**where is the lookup table?**column_index_number:**which column has the value you want returned?**[range_lookup]:**do you want an exact or an approximate match? This is optional and to begin with, I am going to ignore this argument exists.

**HLOOKUP** is similar, but works on a row rather than a column basis.

**Example**

**VLOOKUP**throughout to keep things simple.

**VLOOKUP**always looks for the

**lookup_value**in the first column of a table (the

**table_array**) and then returns a corresponding value so many columns to the right, determined by the

**column_index_number**(column index number).

In this above example, the formula in cell G25 seeks the value 2 in the first column of the table F13:M18 and returns the corresponding value from the eighth column of the table (returning 47). You can follow all of these examples in the attached Excel file.

Pretty easy to understand – so far so good. So what goes wrong? Well, what happens if you add or remove a column from the table range?

Adding (inserting) a column gives us the wrong value:

With a column inserted, the formula contains hard code (8) and therefore, the eighth column (**M**) is still referenced, giving rise to the wrong value. Deleting a column instead may be even worse:

Now there are only seven columns so the formula returns #REF! Oops.

It is possible to make the column index number dynamic using the **COLUMNS **function:

**COLUMNS(reference) counts** the number of columns in the reference. Using the range **F13:M13**, this formula will now keep track of how many columns there are between the lookup column (F) and the result column (**M**). This will prevent the problems illustrated above.

But there’s more issues. Consider duplicate values in the lookup column. With one duplicate, the following happens:

Ah, it looks like it might take the last occurrence. Testing this hypothesis with three duplicates:

**VLOOKUP**takes the last occurrence. Better make sure:

In this instance, **VLOOKUP **cannot even find the value 2!

So what’s going on? The problem – and common modelling mistake – is that the fourth argument has been ignored:

**VLOOKUP(lookup_value, table_array, column_index_number, [range_lookup])**

**[range_lookup] **appears in square brackets, which means it is optional – not that you should place it in square brackets! It has two values:

**TRUE**: this is the default setting if the argument is not specified. Here,**VLOOKUP**will seek an approximate match, looking for the largest value less than or equal to the value sought. There is a price to be paid though: the values in the first column (or row for**HLOOKUP**) must be in strict ascending order – this means that each value must be larger than the value before, so no duplicates.

This is useful when looking up postage rates for example where prices are given in categories of kilograms and you have 2.7kg to post (say). It’s worth noting though that this isn’t the most common lookup when modelling.

**FALSE**: this has to be specified. In this case, data can be any which way – including duplicates – and the result will be based upon the first occurrence of the value sought. If an exact match cannot be found,**VLOOKUP**will return the value #N/A.

And this is the problem highlighted by the above examples. The final argument was never specified so the lookup column data has to be in (strict) ascending order – and this premiss was continually breached.

The robust formula needs both **COLUMNS **and a fourth argument of **FALSE **to work as expected:

This is a quite common mistake in modelling. Using a fourth argument of **FALSE**, **VLOOKUP** will return the corresponding result for the first occurrence of the lookup_value, regardless of number of duplicates, errors or series order. If an approximate match is required, the data must be in strict ascending order.

**VLOOKUP **(and consequently **HLOOKUP**) are not the simple, easy to use functions people think they are. In fact, they can never be used to return data for columns to the left (**VLOOKUP**) or rows above (**HLOOKUP**). So what should modellers use instead..?

Ooh, what a cliff-hanger! More next time.

**Word to the Wise**

As stated above, **HLOOKUP **works like **VLOOKUP **but hunts out a value in the first row of a table and returns a value so many rows below this reference. However, it has the same limitations and should be used just as carefully.