In my previous entry, I presented the LOOKUPVALUE function. In this article, I will explain how you can use the TOPN function to mimic the behavior of VLOOKUP when doing “approximate” searches.
Ken Puls’s article presents a typical use case for this pattern. Let me start with briefly sketching his problem and describe how he solves it.
The effective tax rate problem
Ken uses data representing sales at a daily level. His problem is to determine the effective tax rate for a given row, when tax rates change over time.
In his article, he uses meaningful sales data. Since the core of the problem is to find the relevant tax rate for a specific date, and I want to focus on that, my ‘Data’ table looks like this:
Date |
06/01/2010 |
07/01/2010 |
08/01/2010 |
03/01/2013 |
04/01/2013 |
05/01/2013 |
Here is the ‘Tax Rates’ table. [Start Date] represents the day when a tax rate became effective.
Start Date | Tax Rate |
07/01/2010 | 0,12 |
04/01/2013 | 0,05 |
Note that Gerhard Brueckl’s problem is similar: he presents a way to assign the relevant fiscal period to a date in a dimension table. In his model, fiscal periods are defined by their start date as well.
A solution
Ken Puls’ idea is to add a calculated column to his data. This column will contain the value of [Start Date] for the relevant tax rate. He will then create a relationship between the ‘Data’ table and ‘Tax Rates’ table based on this new column.
His calculated column contains the MAX value of [Start Date] for all rows of ‘Tax Rate’ where ‘Tax Rate’[Start Date] is not greater than the current date in the data table. In other words, he replicates the logic of the following Excel formula:
= VLOOKUP( [Date] , 'Tax Rates', 1, TRUE)
The DAX expression for a calculated column looks like this:
[Tax Rate Start Date] := CALCULATE( MAX( 'Tax Rates'[Start Date]) , FILTER('Tax Rates', 'Tax Rates'[Start Date] <= 'Data'[Date] ) )
It filters the ‘Tax Rates’ table, and calculates the max value of ‘Tax Rates’[Start Date] for the filtered table.
Extending the solution (TOPN)
We can use TOPN to mimic the behaviour of VLOOKUP.
For an approximate search, we will follow this logic:
- use a FILTER expression to eliminate irrelevant rows (as in the previous formula)
- use the TOPN function to filter the result and only return the last row in ‘Tax Rates’
- return the value in the required column for the row we just found.
Here, “the last row” means the row with the latest [Start Date].
Let us rewrite Ken’s expression using the TOPN function:
[Effective Tax Rate - Start Date] := CALCULATE ( VALUES('Tax Rates'[Start Date]) , TOPN(1 , FILTER('Tax Rates', 'Tax Rates'[Start Date] <= 'Data'[Date] ) , 'Tax Rates'[Start Date] ) )
The advantage of this pattern is that we can use it to query any column in our lookup table:
[Effective Tax Rate - Rate] := CALCULATE ( VALUES('Tax Rates'[Tax Rate]) , TOPN(1 , FILTER('Tax Rates', 'Tax Rates'[Start Date] <= 'Data'[Date] ) , 'Tax Rates'[Start Date] ) )
Dealing with ties
There might be a problem with the previous formula: TOPN may return more than 1 row. This will be the case, if there are ties in your data.
In our example, if the ‘Tax Rates’ table has several entries for the same [Start Date], then the VALUES expression might return more than one value. In that case, the expression will fail.
Examples:
Start Date
|
Tax Rate |
Start Date
|
Tax Rate | |
07/01/2010 | 0,12 | 07/01/2010 | 0,12 | |
04/01/2013 | 0,05 | 04/01/2013 | 0,05 | |
07/01/2010 | 0,12 | 07/01/2010 | 0,13 | |
The formula still works. | The formula returns an error. | |||
This may be totally fine. Most of the time, this is what we want: if a reference table contains contradictory information, we want to notice it.
However, in some cases, you may want your formula to ignore that, and take one of these values anyway.
In that case, you can add a sort expression to the TOPN expression, so as to guarantee that only one row (or value) is returned.
You may also use the SAMPLE function instead of the TOPN function. What does it do? Basically the same as TOPN except in case of a tie. In that case, SAMPLE will exactly return the required number of rows.
- Which rows? The engine will decide by itself.
- Will it always return the same rows? This is not guaranteed.
- Will the rows returned change on each execution? This is not guaranteed.
As a side-note, if you want to write a query that returns exactly 3 rows from your ‘Data’ table, but want these rows to be randomly drawn between each execution, then you can do the following.
EVALUATE SAMPLE( 3,'Data', RAND() )
Filed under: OLAP, PowerPivot, SQL Server 2012 Tagged: DAX, LOOKUP in DAX, SAMPLE function in DAX, TOPN, VLOOKUP in DAX