skip to content

DAX Measures

DAX only makes sense once you separate row context from filter context, and CALCULATE is the function that rewrites the second. Expect a question on CALCULATE, iterators or time intelligence — in practice they are the whole language.

on this pageshow

questions

7

In DAX, how does a CALCULATE filter argument combine with the visual's existing filters?

level: middleimportance: must knowfreq 80%

answer

  1. it is not a plain intersection
  2. think about what the sugar expands to
  3. there is a hidden ALL in the rewrite
  4. one modifier turns it back into an intersection

basics

~20 s

A boolean filter argument in CALCULATE replaces any existing filter on the same column rather than intersecting with it, while leaving filters on other columns intact. Wrap it in KEEPFILTERS to intersect instead of overwrite.

solid answer

~50 s

CALCULATE evaluates an expression in a modified filter context. A simple boolean argument such as `Product[Category] = "Accessories"` is shorthand for `FILTER ( ALL ( Product[Category] ), Product[Category] = "Accessories" )` — note the `ALL`. It first removes whatever filter exists on that column and then applies its own, so on a page sliced to `Bikes` the measure still returns Accessories, not blank. Filters on *other* columns are untouched and still intersect normally, so a year slicer keeps working. If you want the argument to narrow the existing selection instead of replacing it, wrap it in `KEEPFILTERS`, and then on a Bikes page you correctly get blank. CALCULATE also does context transition — if a row context is active, the current row becomes a filter — and its filter arguments are all evaluated in the *outer* context before the modification is applied.

code

dax · 14 lines
dax
-- overwrite: ignores a Category slicer, keeps Date/Region slicers
Accessories Sales =
CALCULATE ( [Total Sales], Product[Category] = "Accessories" )

-- what the engine actually evaluates
Accessories Sales Expanded =
CALCULATE (
    [Total Sales],
    FILTER ( ALL ( Product[Category] ), Product[Category] = "Accessories" )
)

-- intersect instead: blank on a Bikes row
Accessories Sales Kept =
CALCULATE ( [Total Sales], KEEPFILTERS ( Product[Category] = "Accessories" ) )

go deeper

for a junior

Recall that CALCULATE modifies the filters an expression sees, and recognise the common patterns such as filtering to one category or removing filters with ALL for a percent-of-total.

for a middle

Explain the overwrite rule precisely — the boolean argument expands to FILTER over ALL of that column — and name KEEPFILTERS as the way to intersect instead. Expect to predict the value on a sliced page.

for a senior

Diagnose the reported symptom 'my measure ignores the slicer' back to overwrite semantics, and choose between ALL, ALLEXCEPT and ALLSELECTED with a reason tied to what the denominator should mean to the reader.

for a principal

Own the consequence at scale: comparison and benchmark measures written with implicit overwrite behave differently once other authors add slicers, so filter-modifier conventions belong in a reviewed measure library rather than in per-report copies.

## CALCULATE is the only function that changes filter context Everything else in DAX reads the filter context; CALCULATE rewrites it. Its signature is `CALCULATE ( <expression>, <filter1>, <filter2>, … )`. It builds a new filter context from the current one plus the filter arguments, then evaluates the expression there. The interview question is almost always about the word "plus" — because it is not a plain intersection. ## Boolean arguments overwrite the column they touch Write this measure: ``` Accessories Sales = CALCULATE ( [Total Sales], Product[Category] = "Accessories" ) ``` and put it in a matrix whose rows are product categories. On the `Bikes` row you might expect blank — the row says Bikes, the measure says Accessories, and the intersection is empty. You get Accessories sales instead, repeated on every row. The reason is that a boolean filter argument is syntax sugar. DAX rewrites it internally as `FILTER ( ALL ( Product[Category] ), Product[Category] = "Accessories" )`. The `ALL` clears the existing filter on `Product[Category]` first; the predicate then reapplies just the categories you named. The engine calls this filter *overwrite*, and it applies per column: only the columns referenced in the filter argument are cleared. That last point is what makes the behaviour useful rather than dangerous. A year slicer, a region slicer and a row header on customer are all on other columns, so they survive untouched and still narrow the result. The measure means "Accessories, under whatever else is selected", which is exactly what you want for a benchmark or comparison measure sitting next to the sliced number. ## KEEPFILTERS restores the intersection When you genuinely want "Accessories **and** whatever is already selected on that column", wrap the argument: ``` Accessories Sales Kept = CALCULATE ( [Total Sales], KEEPFILTERS ( Product[Category] = "Accessories" ) ) ``` Now the new filter is intersected with the existing one instead of replacing it, so the `Bikes` row is blank and the `Accessories` row shows its value. KEEPFILTERS is the single most useful modifier to know here, because "my measure ignores the slicer" is one of the most common bug reports in Power BI and this is usually the fix. ## Filter arguments are evaluated in the outer context All filter arguments are computed *before* the new context exists, in the context CALCULATE was called from. So `CALCULATE ( [Total Sales], Sales[Amount] > [Average Amount] )` compares against the average of the outer context, not against an average recomputed under the new filter. Table-valued filter arguments — a `FILTER(...)` expression, `VALUES`, `TREATAS` — follow the same rule, and they intersect by default in the same per-column way once the columns they carry are determined. ## Removing filters deliberately The other half of CALCULATE's vocabulary is the filter-removal family. `ALL ( Table )` or `ALL ( Table[Column] )` removes filters entirely; `REMOVEFILTERS` is the modern, clearer-reading synonym for that use. `ALLEXCEPT ( Table, Table[Col] )` removes every filter on the table except the listed columns — the standard way to build a "percent of parent" measure. `ALLSELECTED` removes filters coming from inside the visual but respects the user's outer selections, which is why a percent-of-total measure built with ALLSELECTED rebases to what the slicer chose rather than to the entire model. ``` % of Category = DIVIDE ( [Total Sales], CALCULATE ( [Total Sales], ALLEXCEPT ( Product, Product[Category] ) ) ) ``` ## Context transition comes free with CALCULATE If a row context is active when CALCULATE runs, CALCULATE turns that row into filters on the iterated table before applying the filter arguments. Every measure reference is implicitly wrapped in CALCULATE, so calling a measure inside SUMX or a calculated column silently triggers this. It is the mechanism behind per-customer measures inside iterators, and it is also why such patterns get expensive over large fact tables. ## Multiple filter arguments Several filter arguments are combined with AND. Each is applied by the same per-column overwrite rule, so `CALCULATE ( [Total Sales], Product[Category] = "Bikes", 'Date'[Year] = 2024 )` clears and resets both columns. A boolean argument may reference only one column; a condition spanning two columns has to be written as a table filter with `FILTER`. ## What to say under questioning Name the overwrite semantics and the `FILTER(ALL(column), …)` rewrite; state that other columns still intersect; name KEEPFILTERS as the opt-in to intersection; mention that filter arguments evaluate in the outer context; and mention context transition as the second thing CALCULATE does. That covers the function that, in practice, is most of the language.

  • On a page sliced to Bikes, what does CALCULATE([Total Sales], Product[Category] = "Accessories") return?
    Accessories sales, not blank. The boolean argument expands to `FILTER(ALL(Product[Category]), …)`, which clears the Bikes filter on that column before applying its own. Filters on other columns — dates, region, customer — still apply, so the number is Accessories sales under the rest of the selection. Wrapping the argument in KEEPFILTERS would intersect instead and return blank.
  • What is the difference between ALL, ALLEXCEPT and ALLSELECTED as CALCULATE modifiers?
    `ALL` removes filters from the specified table or columns entirely, giving a model-wide denominator. `ALLEXCEPT` removes every filter on a table except the columns you list, which is how percent-of-parent measures keep their grouping. `ALLSELECTED` removes filters coming from inside the visual but honours the user's slicer and page selections, so a percent-of-total rebases to the current selection rather than to the whole model.
  • Why can a boolean filter argument in CALCULATE reference only one column?
    Because the sugar expands to a filter over `ALL` of the referenced column, and that rewrite is defined for a single column. A predicate touching two columns has no single column to clear and reapply, so the engine rejects it. Write it explicitly as a table filter — `FILTER ( Sales, Sales[Qty] > 1 && Sales[Price] > 100 )` — which iterates rows and carries both columns.

saying these in an interview costs you the question

  • Assuming CALCULATE always intersects with existing filters
  • Not knowing the boolean argument hides an ALL on that column
  • Thinking a CALCULATE filter wipes out slicers on unrelated columns
  • Believing filter arguments are evaluated inside the new context
  • Confusing Power BI filter context with a Tableau context filter

context

open as a page

In DAX, what is the difference between row context and filter context?

level: middleimportance: must knowfreq 85%

basics

~20 s

Filter context is the set of filters restricting which model rows a DAX expression sees; it comes from visuals, slicers and CALCULATE. Row context is a pointer to one current row inside a calculated column or an iterator, and it does not filter anything by itself.

open as a page

In DAX, what does DIVIDE do that the / operator does not?

level: juniorimportance: should knowfreq 55%

basics

~20 s

DIVIDE is DAX's safe division function: when the denominator is zero or blank it returns BLANK, or an alternate result you pass as the third argument. The / operator instead yields Infinity or NaN, which then leaks into the visual.

open as a page

In DAX, why is SUMX(Sales, Sales[Qty] * Sales[Price]) not SUM(Sales[Qty]) * SUM(Sales[Price])?

level: middleimportance: should knowfreq 65%

basics

~20 s

SUMX multiplies quantity by price inside each row and then adds the results, so every row uses its own price. Multiplying two SUMs multiplies a total quantity by a total price, which is a number no transaction ever had.

open as a page

In DAX, why does SAMEPERIODLASTYEAR return blank or wrong values without a proper date table?

level: seniorimportance: should knowfreq 50%

basics

~20 s

DAX time-intelligence functions shift a set of dates and reapply it as a filter, which requires a dedicated date table with one row per contiguous calendar day, related to the fact table and marked as a date table. Gaps, duplicates, or filtering the fact table's own date column break the shift.

open as a page

In a Power BI matrix, why doesn't a DAX measure's total row equal the sum of the rows?

level: seniorimportance: should knowfreq 60%

basics

~20 s

The total is not a sum of the displayed cells. Power BI evaluates the same DAX measure again in the total's filter context, with the row grouping removed, so any non-additive measure — a ratio, a distinct count, a conditional — legitimately returns something else.

open as a page

In DAX, why doesn't CALCULATE change the value of a VAR defined before it?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

A DAX variable is evaluated once, in the context where it is defined, and its result is then a constant. CALCULATE later in the expression modifies the filter context for what follows, but cannot re-evaluate a value that has already been computed.

open as a page