The new DAX window functions (announced here, more details on Jeffrey Wang’s blog here and here) have generated a lot of excitement already – they are extremely powerful. However one important benefit of using them has not been mentioned so far: they can give you much better performance in DirectQuery mode because they make it more likely that aggregations are used. After all, the fastest DirectQuery datasets are the ones that can use aggregations (ideally Import mode aggregations) as much as possible.
To illustrate this, here’s a very simple dataset with a fact table in DirectQuery mode and a Date dimension table in Dual mode built on the SQL Server AdventureWorksDW2017 sample database:
Let’s start off with a simple measure that sums up the values in the SalesAmount column:
Sales Amount = SUM('Internet Sales'[SalesAmount])
When you use it in a table visual with the CalendarYear column from the Date table like so:
…Power BI can get the data it needs with a single SQL query. I won’t show the whole query here, but it’s a simple Group By and returns exactly what you’d expect if you run it in SQL Server Management Studio:
Now let’s say you want to do a year-on-year growth calculation. To do this, you’ll need to be able to find the Sales Amount for the previous year. One way to do this in DAX would be to use the SamePeriodsLastYear function like so:
LY Sales Amount V1 = CALCULATE ( [Sales Amount], SAMEPERIODLASTYEAR ( 'Date'[FullDateAlternateKey] ) )
Used in a table it gives the correct result:
However the SQL query generated for this visual is now a lot more, ahem, verbose and because the DAX time intelligence functions are all resolved at the date granularity it now returns Sales Amount summed up by date rather than by year:
If you write the same previous year sales measure using the new Offset function like so:
LY Sales Amount V2 = CALCULATE ( [Sales Amount], OFFSET ( -1, ALLSELECTED ( 'Date'[CalendarYear] ), ORDERBY ( 'Date'[CalendarYear], ASC ) ) )
…you get the same result as before:
…but now the SQL query is much simpler and returns data at the year granularity, as you’d want:
I’m not a SQL expert so I won’t comment on the SQL generated – besides, it may change in the future – but the most important implication of this is that the version of the measure that uses Offset is more likely to be able to use aggregations.
For example, if you add an extra import-mode table to the dataset with the data from the fact table aggregated to year granularity:
…and set it up as an aggregation table:
…then any queries at the year granularity should use it. As you would expect, the query for visual shown above with the measure using SamePeriodLastYear misses the aggregation and goes to the DirectQuery fact table:
The query for the measure using Offset, though, can use the aggregation and there is no DirectQuery activity at all:
This is just one example. I’m almost certain there are other ways to write this calculation without using Offset which will also hit the aggregation but they won’t be as elegant as the Offset version. What’s more, as your measures get more and more complex it gets harder and harder to write DAX that results in simple, efficient SQL in DirectQuery mode and the new window functions are a massive help here.