Why DAX Window Functions Are Important For Performance In Power BI DirectQuery Mode

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.

14 thoughts on “Why DAX Window Functions Are Important For Performance In Power BI DirectQuery Mode

  1. Thanks for this post, I’m thankful for the new visual functions, and look forward to using it at work. Seems to be an alternate to using custom time intelligence? FILTER(ALL… Do you believe it will bring better performance to this popular pattern?

  2. Hi Chris,
    I tested the same example, and the query with the measure using the time intelligence function “SamePeriodLastYear” fetched the data from the aggregation table and did not use the DirectQuery fact table.

  3. I tested the same example, and the query with the measure using the time intelligence function “SamePeriodLastYear” fetched the data from the aggregation table and did not use the DirectQuery fact table.

    1. Are you sure? Can you post more details? I would be very surprised if the version using SamePeriodLastYear could use an aggregation at the Year level.

      1. For the first example without the aggregated table, I confirm that the SAMEPERIODLAST query is worse, because the SQL query generates results by date-time granularity instead of by year, but with the OFFSET function the generated SQL query is simpler and returns results at year level.

        For the example using the aggregated table, the SAMEPERIODLASTYEAR and OFFSET functions reach the aggregated table and do not produce an SQL query.

        I couldn’t post screenshots here in the comments

  4. Thanks for post. One day it would be nice to have AI driven “Copilot like” experience when writing DAX to tell me when I’m doing it wrong or could optimise my code. It could perhaps test its assertions behind the scenes to check out the generated plans and plan costs to arrive at these suggestions.

  5. So would you recommend using Window functions every time we have a SAMEPERIODLASTYER? In general, WHEN would you recommend using WINDOW functions instead of time intelligence? Only for performance? Other reasins?

    1. I recommend using Window functions in DirectQuery mode when you have aggregations and even when you don’t. For Import mode I don’t think anyone knows enough to make general statements right now.

  6. Are the DAX Window functions available in the Power Pivot Excel add-in? The documentation on dax.guide does not list Excel on the compatibility list for Offset, OrderBy, etc….

    Thanks

Leave a Reply