Advanced DAX Calculations
Introduction
This lesson continues the domain Implement and manage semantic models (25–30% of the DP-600 skills measured) with the skills that turn a model into a reusable calculation layer: write calculations that use DAX variables and functions such as iterators, table filtering, windowing and information functions, and implement calculation groups, dynamic format strings and field parameters. This course targets the English syllabus effective October 19, 2026. Microsoft does not publish the question count or the question-type mix, so OnLevelUp mock exams use a 50-question practice length and an unofficial 80% practice target, which is not equivalent to the official 700/1000 scaled passing score.
Lesson 7 introduced DAX queries and basic measures. Here you go further: you read a measure and predict its result, choose between an iterator and a plain aggregator, avoid repeated computation with variables, rank and compare rows with window functions, and remove duplicated measures with calculation groups. After this lesson you will be able to write each of these constructs and to recognize which one a scenario calls for.
Learning objectives
- Use
VARandRETURNto make measures readable and faster. - Explain filter context, row context and context transition, and choose between aggregators and iterators.
- Filter tables with
FILTER,ALL,ALLSELECTED,KEEPFILTERSandCALCULATETABLE. - Use window functions (
INDEX,OFFSET,WINDOW,RANK,ROWNUMBER) and information functions (ISBLANK,ISFILTERED,HASONEVALUE,SELECTEDVALUE,USERPRINCIPALNAME). - Create calculation groups, dynamic format strings and field parameters.
1. Evaluation contexts in one page
Almost every DAX surprise comes from two contexts.
| Context | Created by | Effect |
|---|---|---|
| Filter context | Slicers, rows and columns of a visual, filters, CALCULATE |
Restricts the rows of tables before the expression is evaluated |
| Row context | Calculated columns, iterators (SUMX, FILTER, ADDCOLUMNS) |
Gives access to the current row's values, one row at a time |
Context transition. Inside a row context, CALCULATE (and any measure reference, which is wrapped in an implicit CALCULATE) converts the current row into filters. This is why SUMX ( DimCustomer, [Total Sales] ) returns each customer's sales and adds them up: for each customer row, the measure is evaluated with that customer as a filter.
CALCULATE ( <expression>, <filter1>, <filter2> ... ) modifies the filter context. A Boolean filter such as DimProduct[Color] = "Red" replaces any existing filter on that column. Filters in a CALCULATE are combined with AND across different columns, and each argument is evaluated in the original context.
Red Sales = CALCULATE ( [Total Sales], DimProduct[Color] = "Red" )
All Sales = CALCULATE ( [Total Sales], ALL ( DimProduct ) )
Share of All = DIVIDE ( [Total Sales], [All Sales] )2. Variables
A variable is declared with VAR and used after RETURN. It is evaluated once, at the point of declaration, in the filter context where it is declared, and the result is reused.
Sales vs Prior Year % =
VAR CurrentSales = [Total Sales]
VAR PriorSales =
CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( DimDate[Date] ) )
RETURN
IF ( NOT ISBLANK ( PriorSales ), DIVIDE ( CurrentSales - PriorSales, PriorSales ) )Benefits:
| Benefit | Why |
|---|---|
| Performance | A sub-expression used three times is computed once |
| Readability | Names such as PriorSales document intent |
| Debugging | You can RETURN a variable to inspect an intermediate value |
| Correctness | The value is frozen before a later CALCULATE changes the filter context |
A common trap: a variable captured before a CALCULATE keeps the old context.
Wrong Share =
VAR S = [Total Sales]
RETURN DIVIDE ( S, CALCULATE ( S, ALL ( DimProduct ) ) ) -- always 1: S is already a fixed numberCALCULATE ( S, ALL ( DimProduct ) ) cannot change S because S was evaluated earlier. The correct form calls the measure inside the CALCULATE.
Example. A margin measure refers to [Revenue] and [Cost] four times in nested IF branches. Rewriting with VAR Revenue = [Revenue] and VAR Cost = [Cost] shortens it and cuts the number of storage-engine queries.
3. Aggregators and iterators
An aggregator (SUM, AVERAGE, MIN, MAX, COUNT) works on one column. An iterator (SUMX, AVERAGEX, MINX, MAXX, COUNTX, RANKX, CONCATENATEX) evaluates an expression row by row over a table and then aggregates.
| Need | Use |
|---|---|
| Sum of a stored column | SUM ( FactSales[NetAmount] ) |
| Sum of a per-row calculation | SUMX ( FactSales, FactSales[Quantity] * FactSales[UnitPrice] ) |
| Average of a per-customer total | AVERAGEX ( VALUES ( DimCustomer[CustomerKey] ), [Total Sales] ) |
| Count of distinct values | DISTINCTCOUNT ( FactSales[CustomerKey] ) |
| Maximum of a measure by group | MAXX ( VALUES ( DimProduct[Category] ), [Total Sales] ) |
Worked example: average order value is not the average of the amount column.
Statement. FactSales has one row per order line. Management asks for "average sales per customer". A junior analyst writes AVERAGE ( FactSales[NetAmount] ) and reports 38 €. Another analyst writes the iterator version and reports 412 €.
Reasoning.
AVERAGEof the amount column gives the average order line, because the grain of the table is one line.- "Per customer" needs customers as the unit: first compute total sales per customer, then average those totals.
VALUES ( DimCustomer[CustomerKey] )returns the customers in the current filter context;AVERAGEXevaluates[Total Sales]for each (context transition) and averages.
Avg Sales per Customer =
AVERAGEX ( VALUES ( DimCustomer[CustomerKey] ), [Total Sales] )Solution. The iterator version, 412 €.
Why the other is wrong. It answers a different question (average line value). The error comes from not matching the unit of analysis to the grain of the table.
Iterators over large tables cost more than aggregators, since the engine may need the formula engine to evaluate the expression per row. Iterate over a small table (such as the distinct values of a dimension column) when you can, not over the fact table, unless the per-row expression is required.
4. Table filtering
4.1 FILTER versus Boolean filters
A Boolean filter argument in CALCULATE is translated into a filter over one column, which is efficient. FILTER iterates a table and can use measures or multiple columns, which is more powerful but slower.
-- Efficient: one column predicate
High Value Sales = CALCULATE ( [Total Sales], FactSales[NetAmount] > 1000 )
-- Needed when the condition uses a measure
Big Customers Sales =
CALCULATE ( [Total Sales], FILTER ( VALUES ( DimCustomer[CustomerKey] ), [Total Sales] > 50000 ) )Do not write FILTER ( ALL ( FactSales ), ... ) over a fact table when a column predicate is enough; it forces the engine to materialize the whole table.
4.2 Removing and keeping filters
| Function | Effect |
|---|---|
ALL ( table or columns ) |
Removes all filters on the table or the columns |
ALLEXCEPT ( table, col1, ... ) |
Removes all filters on the table except those on the listed columns |
ALLSELECTED ( ... ) |
Removes the filters of the current visual but keeps the outer filters (slicers, page filters) |
REMOVEFILTERS ( ... ) |
Same as ALL when used as a CALCULATE modifier, clearer in intent |
KEEPFILTERS ( filter ) |
Intersects the new filter with the existing filter instead of replacing it |
VALUES ( col ) / DISTINCT ( col ) |
Currently visible values (VALUES can include a blank member) |
Example. "Percent of the visible total" in a matrix with a year slicer: DIVIDE ( [Total Sales], CALCULATE ( [Total Sales], ALLSELECTED ( DimProduct ) ) ). With ALL instead of ALLSELECTED the denominator would ignore the slicer on products and show a share of the whole company.
KEEPFILTERS matters with row-level security and with slicers: CALCULATE ( [Total Sales], KEEPFILTERS ( DimProduct[Color] = "Red" ) ) returns blank if the user's current selection excludes Red, instead of overriding the selection.
4.3 CALCULATETABLE and table expressions
CALCULATETABLE is the table-returning sibling of CALCULATE. Use it with SUMMARIZECOLUMNS, ADDCOLUMNS and TOPN to build tables in a query (lesson 7) or in a calculated table.
Top 5 Products =
TOPN ( 5, ALL ( DimProduct[ProductName] ), [Total Sales] )5. Window functions
DAX window functions return rows relative to a position inside a partition of an ordered table, without writing complex time-intelligence or EARLIER logic. They work on any ordering, not only dates.
| Function | Returns |
|---|---|
INDEX ( position, relation, orderBy, ... ) |
The row at an absolute position (for example the first, last or third) |
OFFSET ( delta, relation, orderBy, ... ) |
The row a number of positions before or after the current row |
WINDOW ( from, fromType, to, toType, relation, orderBy, ... ) |
A range of rows (running totals, moving averages) |
RANK, ROWNUMBER |
The rank or sequential number of the current row |
The relation is usually built with ALLSELECTED ( DimDate[Month], DimDate[MonthNumber] ) or a similar table, then ordered with ORDERBY, and optionally split with PARTITIONBY.
Sales Previous Month =
CALCULATE (
[Total Sales],
OFFSET ( -1, ALLSELECTED ( DimDate[YearMonth] ), ORDERBY ( DimDate[YearMonth], ASC ) )
)
Moving 3-Month Sales =
CALCULATE (
[Total Sales],
WINDOW ( -2, REL, 0, REL, ALLSELECTED ( DimDate[YearMonth] ), ORDERBY ( DimDate[YearMonth], ASC ) )
)
Product Rank =
RANK ( DENSE, ALLSELECTED ( DimProduct[ProductName] ), ORDERBY ( [Total Sales], DESC ) )REL means relative positions (-2 to 0 is the two previous rows and the current one); ABS means absolute positions from the start of the partition. Window functions can also work on gaps in time by relying on the ordering of existing rows, unlike DATEADD, which depends on calendar logic. Use PARTITIONBY (for example by year) to restart a running total each year.
Worked example: running total that restarts each year.
Statement. A report shows months in rows and must display year-to-date sales and the change versus the previous month.
Reasoning.
- A cumulative total from the first row of each year:
WINDOW ( 1, ABS, 0, REL, ... , PARTITIONBY ( DimDate[Year] ) )(from the first row of the partition to the current row). - The previous month:
OFFSET ( -1, ... )in the same ordering. - For the first month of a year, the previous month is in the previous year; with
PARTITIONBYby year,OFFSETreturns blank there. If the change must cross the year boundary, drop the partition in theOFFSETrelation.
Solution. Two measures sharing the same relation and ordering, one partitioned by year for the running total and one not partitioned for the month-over-month change.
Why not an iterator with FILTER on dates. It works, but each cell repeats a scan of the date table and it is harder to read and to adapt.
Exam tip. The study guide lists "window functions" explicitly. Know the roles of
INDEX,OFFSET,WINDOW,RANK,ROWNUMBER, and the parametersORDERBY,PARTITIONBY,REL/ABS.
6. Information functions
Information functions test the state of values or the filter context so a measure can adapt.
| Function | Use |
|---|---|
ISBLANK ( x ) |
Detect a blank result, often to avoid showing zeros |
ISFILTERED ( col ) |
True if the column is directly filtered |
ISCROSSFILTERED ( col ) |
True if the column is filtered directly or through a relationship |
HASONEVALUE ( col ) / SELECTEDVALUE ( col, alt ) |
One value in context, to read a slicer or detect a total row |
ISINSCOPE ( col ) |
True if the column is a level in the visual's current grouping (reliable for hierarchies) |
USERPRINCIPALNAME ( ) |
The signed-in user, used in dynamic row-level security |
CONTAINSSTRING, ISNUMBER, ISTEXT, ISERROR |
Value tests |
Detecting a total row correctly:
Margin % Display =
IF ( ISINSCOPE ( DimProduct[ProductName] ), [Margin %], BLANK () )This hides the percentage at category and total levels where it would be misleading. HASONEVALUE is an older alternative that fails when a column has one value only because a filter selected one.
Dynamic row-level security example: a role with the filter DimUser[Email] = USERPRINCIPALNAME () on a security table related to the fact table.
7. Calculation groups
A calculation group is a table with one column (for example Time Calc) whose rows are calculation items. Each item is a DAX expression that modifies any measure by using SELECTEDMEASURE (). One group with five items replaces dozens of measures such as "Sales YTD", "Margin YTD", "Cost YTD".
-- Calculation group: Time Calc
-- Item "Current"
SELECTEDMEASURE ()
-- Item "YTD"
CALCULATE ( SELECTEDMEASURE (), DATESYTD ( DimDate[Date] ) )
-- Item "Prior Year"
CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( DimDate[Date] ) )
-- Item "YoY %"
VAR Current = SELECTEDMEASURE ()
VAR Prior = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( DimDate[Date] ) )
RETURN DIVIDE ( Current - Prior, Prior )Facts to know:
| Topic | Detail |
|---|---|
| Authoring | Power BI Desktop model view (Calculation group), Tabular Editor, or the web model editing experience |
| Prerequisite | Implicit measures must be disabled (Desktop does this when you create the first calculation group) |
| Precedence | When several groups apply, the precedence number decides the order; higher precedence is applied first |
| Format | Each item can have a format string expression |
| Direct Lake | Supported in all Direct Lake scenarios |
| Related functions | SELECTEDMEASURE, SELECTEDMEASURENAME, ISSELECTEDMEASURE |
ISSELECTEDMEASURE ( [Margin %], [Units] ) lets an item skip measures where it makes no sense, for example not applying "YoY %" to a ratio.
Example. A manufacturer has 12 base measures and wants current, YTD, prior year and YoY %. Without a calculation group that is 48 measures; with one group of four items it is 12 measures and four items.
7.1 Pitfalls
- Calculation items apply to measures, not to columns used in a visual.
- Two groups touching the same dimension need an explicit precedence plan.
- Adding a calculation group can make the data type of measures variant, which affects dynamic format strings; wrap the logic in
FORMATonly if you must.
8. Dynamic format strings
A dynamic format string changes how a measure is displayed according to context, while the measure stays numeric. This is better than FORMAT (), which returns text and breaks charts and sorting.
-- Format string expression of the measure [Revenue]
SWITCH (
TRUE (),
SELECTEDVALUE ( DimCurrency[Code] ) = "EUR", "#,0.00 €",
SELECTEDVALUE ( DimCurrency[Code] ) = "USD", "$#,0.00",
"#,0.00"
)
-- Show thousands or millions automatically
SWITCH (
TRUE (),
ABS ( SELECTEDMEASURE () ) >= 1000000, "#,0.0,, \M",
ABS ( SELECTEDMEASURE () ) >= 1000, "#,0.0, \K",
"#,0"
)Commas after the digit placeholders scale by thousands, and a backslash escapes a literal letter. Dynamic format strings can be applied to one measure or to a calculation item, so that a "YoY %" item always shows a percentage while "Current" keeps the measure's format. When a calculation group and a measure both define a format, the calculation group item with the highest precedence wins.
Worked example: multi-currency display.
Statement. A model has one [Revenue] measure in a reporting currency. A slicer DimCurrency[Code] selects EUR, USD or JPY, and the numbers must use the matching symbol and decimals (no decimals for JPY). The visual must stay numeric so a column chart sorts correctly.
Reasoning.
- Using
FORMATwould return text: the chart axis would sort alphabetically and totals would not work. - Add a dynamic format string to
[Revenue]: aSWITCHonSELECTEDVALUE ( DimCurrency[Code] ). - Return a different format string per code, and a neutral default when several currencies are selected.
Solution. Dynamic format string on the measure with SELECTEDVALUE.
Why the others are worse. FORMAT breaks numeric behavior; a separate measure per currency multiplies the model's measures; changing the column format in the lakehouse is static.
9. Field parameters
A field parameter lets report users switch which fields or measures a visual shows, through a slicer. Power BI creates a calculated table with a NAMEOF list:
Metric Selector = {
( "Revenue", NAMEOF ( [Revenue] ), 0 ),
( "Margin", NAMEOF ( [Margin] ), 1 ),
( "Units", NAMEOF ( [Units] ), 2 )
}The three columns are the display name, the field reference and the sort order. You create it in Power BI Desktop (Modeling > New parameter > Fields), then place the parameter column in a visual's axis or values and the display-name column in a slicer. Field parameters can contain columns (to switch a dimension on an axis) or measures (to switch the metric).
| Feature | Field parameter | Calculation group |
|---|---|---|
| Changes | Which field a visual shows | How a measure is calculated |
| Created from | Desktop UI or DAX table of NAMEOF |
Calculation items |
| Typical use | Let users pick metric or dimension | Time intelligence, currency, scenario |
| Direct Lake | Supported | Supported |
Common pitfalls
- Capturing a measure in a variable and then trying to change its filter context with
CALCULATE. - Using
FILTER ( ALL ( FactTable ), ... )where a Boolean column filter is enough. - Using
ALLwhereALLSELECTEDis needed, which makes percentages ignore slicers. - Averaging a column when the question is average per entity.
- Using
FORMATfor display and then wondering why a chart sorts as text. - Testing a total row with
HASONEVALUEwhereISINSCOPEis the reliable choice. - Applying a calculation item to a ratio measure without
ISSELECTEDMEASURE. - Writing hundreds of near-identical time-intelligence measures instead of a calculation group.
Exam tip. The question describes a symptom (wrong share, text-sorted axis, 48 duplicate measures) and asks for the construct. Map: wrong denominators →
ALLvsALLSELECTED/variables; text-sorted display → dynamic format string; duplicated time measures → calculation group; user chooses metric → field parameter; previous row or running total → window function.
Mini case study
Context. Contoso Retail has a semantic model with [Revenue], [Cost], [Margin], [Margin %]. Requirements: (1) users must view each measure as current, year-to-date or year-over-year; (2) the YoY item must show a percentage; (3) executives want a visual where they can pick Revenue, Margin or Units; (4) a ranking of the top 3 stores by revenue inside the selected region; (5) a total row that does not display margin percentage at category level.
Solution.
- A calculation group
Time Calcwith items Current, YTD, Prior Year and YoY %. - A dynamic format string on the YoY % item returning
"0.0%", so the measure stays numeric. - A field parameter
Metric Selectorwith the three measures, used in the visual values and a slicer. - A measure
Store Rank = RANK ( DENSE, ALLSELECTED ( DimStore[StoreName] ), ORDERBY ( [Revenue], DESC ) )with a visual-level filterStore Rank <= 3;ALLSELECTEDkeeps the region slicer. Margin % Display = IF ( ISINSCOPE ( DimProduct[ProductName] ), [Margin %] ).
Comment. Each requirement maps to one construct, and none requires duplicating measures. Adding the calculation group requires implicit measures to be off, which should be communicated to report authors.
Key terms
- Filter context / row context: sets of filters or the current row used when evaluating DAX.
- Context transition: conversion of a row context into filters by
CALCULATE. - Variable (
VAR): named value evaluated once. - Iterator: function evaluating an expression row by row (
SUMX,AVERAGEX). - Window function:
INDEX,OFFSET,WINDOW,RANK,ROWNUMBER. - Calculation group: table of calculation items modifying measures via
SELECTEDMEASURE. - Dynamic format string: DAX expression returning a format string for a measure or item.
- Field parameter: calculated table that switches fields or measures in visuals.
Summary checklist
- I can explain filter context, row context and context transition.
- I use variables for reuse and readability and know their frozen-context behavior.
- I choose between aggregators and iterators from the grain of the question.
- I can use
ALL,ALLSELECTED,KEEPFILTERS,FILTERandCALCULATETABLEcorrectly. - I can write
OFFSET,WINDOWandRANKmeasures withORDERBYandPARTITIONBY. - I can use
ISINSCOPE,SELECTEDVALUEandUSERPRINCIPALNAME. - I can build a calculation group with a dynamic format string and a field parameter.
Official sources
- Study guide for Exam DP-600: https://learn.microsoft.com/en-us/credentials/certifications/resources/study-guides/dp-600 (opens in a new tab)
- Create calculation groups in Power BI: https://learn.microsoft.com/en-us/power-bi/transform-model/calculation-groups (opens in a new tab)
- Create dynamic format strings for measures: https://learn.microsoft.com/en-us/power-bi/create-reports/desktop-dynamic-format-strings (opens in a new tab)
- DAX reference: https://learn.microsoft.com/en-us/dax/ (opens in a new tab)
- Direct Lake overview: https://learn.microsoft.com/en-us/fabric/fundamentals/direct-lake-overview (opens in a new tab)
