DAX CALCULATE Explained: Filter Context in Plain English
CALCULATE is the most important DAX function and the most confusing. Learn filter context, row context and context transition with simple examples.
Short answer: Time intelligence only works with a proper date table: one row per day, no gaps, marked as the date table. With that in place,
TOTALYTDgives year-to-date,SAMEPERIODLASTYEARgives the prior year, andDATESINPERIODgives a rolling window. Each is aCALCULATEthat swaps the time filter.
“How are we doing versus last year?” is probably the most common question a report gets asked. It is also where many beginners meet DAX errors that read like riddles.
The good news is that the patterns are short. The catch is the date table. Names below are illustrative.
Time intelligence functions shift across a sequence of dates, so they need that sequence to be complete. The rules, as summarised in several guides, are:
If your date column comes from the sales table, it only has dates that had sales, so weekends or holiday gaps can quietly break the calculations. Build a separate Date table instead. I show how to generate one, including fiscal columns, in SQL date functions and fiscal calendars, and the same idea works in Power Query or DAX:
Date =
ADDCOLUMNS(
CALENDAR( DATE(2022,1,1), DATE(2027,12,31) ),
"Year", YEAR([Date]),
"Month Number", MONTH([Date]),
"Month", FORMAT([Date], "MMM")
)
Then mark it as the date table and create a one-to-many relationship from Date[Date] to your fact table. This is a standard part of a star schema.
TOTALYTD evaluates an expression over the dates from the start of the year up to the current point. Here is the measure:
Sales YTD = TOTALYTD( [Total Sales], 'Date'[Date] )
If your fiscal year doesn’t end on 31 December, pass the year-end date as the last argument. The DAX Guide documents the optional year-end parameter, for example a June year end:
Sales Fiscal YTD = TOTALYTD( [Total Sales], 'Date'[Date], "6/30" )
The equivalent using CALCULATE, which is worth learning because it shows what is happening underneath:
Sales YTD (long form) =
CALCULATE( [Total Sales], DATESYTD( 'Date'[Date] ) )
If CALCULATE still feels fuzzy, read DAX CALCULATE explained first.
Two steps: get last year’s value, then compare.
Sales LY = CALCULATE( [Total Sales], SAMEPERIODLASTYEAR( 'Date'[Date] ) )
Sales YoY % =
VAR CurrentValue = [Total Sales]
VAR LastYear = [Sales LY]
RETURN DIVIDE( CurrentValue - LastYear, LastYear )
According to the DAX Guide, SAMEPERIODLASTYEAR is equivalent to DATEADD( dates, -1, YEAR ). That is useful, because DATEADD is the flexible version: change the number and the interval to compare with last quarter or two years ago.
DIVIDE is worth using instead of /, because it returns blank instead of an error when last year is zero.
A rolling window moves with the latest date in the current filter. DATESINPERIOD returns a run of dates starting at a given point and continuing for a number of intervals:
Sales Rolling 12M =
CALCULATE(
[Total Sales],
DATESINPERIOD( 'Date'[Date], MAX( 'Date'[Date] ), -12, MONTH )
)
Read it aloud: “take the last date in view, go back 12 months, and sum the sales in that window.” On a monthly chart, each point becomes the trailing twelve months, which smooths out seasonality nicely.
For YoY, a line chart with two lines (this year and last year) or a column chart with a variance number beside it. Put the YoY percentage in a card, and put the rolling 12-month line on the same axis as the monthly values for context.
My take: write the measure with variables and readable names, then never touch it again. Time intelligence is boring in the best way when it is set up once. If your report only ever looks at a single month, you can skip all of this.
If a report using these measures becomes slow, see 7 fixes in order of payoff.
Sources: DAX Guide: TOTALYTD, DAX Guide: SAMEPERIODLASTYEAR, Microsoft Learn: Use DAX time intelligence functions. Date-table requirements are summarised from these and other guides. Table names and figures are illustrative.
Part of: Power BI & DAX