Skip to content

Advanced DAX Calculations

Step 17 of 20

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 VAR and RETURN to 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, KEEPFILTERS and CALCULATETABLE.
  • 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.

DAX
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.

DAX
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.

DAX
Wrong Share =
VAR S = [Total Sales]
RETURN DIVIDE ( S, CALCULATE ( S, ALL ( DimProduct ) ) )  -- always 1: S is already a fixed number

CALCULATE ( 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.

  1. AVERAGE of the amount column gives the average order line, because the grain of the table is one line.
  2. "Per customer" needs customers as the unit: first compute total sales per customer, then average those totals.
  3. VALUES ( DimCustomer[CustomerKey] ) returns the customers in the current filter context; AVERAGEX evaluates [Total Sales] for each (context transition) and averages.
DAX
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.

DAX
-- 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.

DAX
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.

DAX
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.

  1. 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).
  2. The previous month: OFFSET ( -1, ... ) in the same ordering.
  3. For the first month of a year, the previous month is in the previous year; with PARTITIONBY by year, OFFSET returns blank there. If the change must cross the year boundary, drop the partition in the OFFSET relation.

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 parameters ORDERBY, 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:

DAX
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".

DAX
-- 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 FORMAT only 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.

DAX
-- 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.

  1. Using FORMAT would return text: the chart axis would sort alphabetically and totals would not work.
  2. Add a dynamic format string to [Revenue]: a SWITCH on SELECTEDVALUE ( DimCurrency[Code] ).
  3. 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:

DAX
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 ALL where ALLSELECTED is needed, which makes percentages ignore slicers.
  • Averaging a column when the question is average per entity.
  • Using FORMAT for display and then wondering why a chart sorts as text.
  • Testing a total row with HASONEVALUE where ISINSCOPE is 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 → ALL vs ALLSELECTED/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.

  1. A calculation group Time Calc with items Current, YTD, Prior Year and YoY %.
  2. A dynamic format string on the YoY % item returning "0.0%", so the measure stays numeric.
  3. A field parameter Metric Selector with the three measures, used in the visual values and a slicer.
  4. A measure Store Rank = RANK ( DENSE, ALLSELECTED ( DimStore[StoreName] ), ORDERBY ( [Revenue], DESC ) ) with a visual-level filter Store Rank <= 3; ALLSELECTED keeps the region slicer.
  5. 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, FILTER and CALCULATETABLE correctly.
  • I can write OFFSET, WINDOW and RANK measures with ORDERBY and PARTITIONBY.
  • I can use ISINSCOPE, SELECTEDVALUE and USERPRINCIPALNAME.
  • I can build a calculation group with a dynamic format string and a field parameter.

Official sources