Forum Discussion
DAX Behavior
- 5 months ago
HI EfratY
Here are the main points explaining what's going on:
- Any measure reference in DAX is automatically wrapped in a hidden
CALCULATE.
In this case,[Average Retail Price]is treated asCALCULATE ( [Average Retail Price] ). - Calling the
CALCULATEfunction within a row context causes context transition, where the row context is transformed into an equivalent filter context before evaluating the first argument ofCALCULATE. - In the
High Ticket Ordersmeasure, theFILTERfunction is an iterator which iterates over'Product Lookup', evaluating the 2nd argument expressionProduct Lookup'[ProductPrice] > [Average Retail Price]within a row context corresponding to each row of'Product Lookup'. - Due to this context transition, the values of all columns of
'Product Lookup'in the "current row", including'Product Lookup'[ProductPrice], are added as filters when evaluating[Average Retail Price]. This means[Average Retail Price]will return the average of just the single price on the current row iterated byFILTER. In other words[Average Retail Price]will return the same value as'Product Lookup'[ProductPrice]for any given row of'Product Lookup'iterated byFILTER. - As a result,
'Product Lookup'[ProductPrice] > [Average Retail Price]is logically equivalent to'Product Lookup'[ProductPrice] > 'Product Lookup'[ProductPrice], which is always false (a value is never greater than itself). - If you replace
[Average Retail Price]with[Overall Average Price], context transition still happens, but[Overall Average Price]removes filters on'Product Lookup'viaALL ( 'Product Lookup' )so the filters due to context transition are ignored. The value returned by[Overall Average Price]would however ignore any existing filters on'Product Lookup'so would not change due to any existing filters on'Product Lookup'. - Suggested solution: I would generally recommend storing the value of
[Average Retail Price]in a variable before callingFILTER. This avoids the whole context transition complication and should return the intended value. I would also recommend changingFILTER ( ... )to a boolean condition withinKEEPFILTERS. See below:
High Ticket Orders improved v1 = VAR AveragePrice = [Average Retail Price] RETURN CALCULATE ( [Total Orders], FILTER ( 'Product Lookup', 'Product Lookup'[ProductPrice] > AveragePrice ) )High Ticket Orders improved v2 = VAR AveragePrice = [Average Retail Price] RETURN CALCULATE ( [Total Orders], KEEPFILTERS ( 'Product Lookup'[ProductPrice] > AveragePrice ) )Some good reading on this topic:
https://www.sqlbi.com/articles/understanding-context-transition-in-dax/
- Any measure reference in DAX is automatically wrapped in a hidden
The key difference comes down to filter context (and how row context from the FILTER iterator interacts with it) during evaluation of the condition inside High Ticket Orders.
Your High Ticket Orders measure builds a filtered version of the 'Product Lookup' table and then uses that as a table filter argument inside CALCULATE. The FILTER function iterates row-by-row over 'Product Lookup' (in whatever outer filter context exists—in your empty Matrix visual, that's the full/unfiltered model). For each row, it evaluates the boolean condition:
'Product Lookup'[ProductPrice] > [Some Average Measure]
- The left side ([ProductPrice]) is a direct column reference → evaluated in row context (the current product's price).
- The right side is a measure reference → measures always evaluate in filter context (never directly in row context).
Here's where the behavior diverges:
When you use [Average Retail Price] (the one that returns blank)
- [Average Retail Price] = AVERAGE('Product Lookup'[ProductPrice]) is a simple aggregator with no CALCULATE or ALL.
- During the row-by-row iteration of FILTER('Product Lookup', ...), the evaluation of this measure ends up seeing a filter context that is effectively restricted to the current single row being iterated (this is a common DAX "gotcha" when the measure aggregates the exact same table the iterator is scanning, even though row context doesn't normally auto-transition to filter context).
- Result: For every product row, [Average Retail Price] evaluates to that row's own ProductPrice (i.e. the average of one value = the value itself).
- The condition becomes ProductPrice > ProductPrice → always false.
- FILTER therefore returns an empty table.
- CALCULATE([Total Orders], <empty table filter on Product Lookup>) propagates no products → no related sales rows → DISTINCTCOUNT returns blank.
This happens even in your empty Matrix (grand-total context with no slicers/row/column fields), because the per-row evaluation inside FILTER is what introduces the restrictive context.
When you use [Overall Average Price] (the one that works)
- [Overall Average Price] = CALCULATE([Average Retail Price], ALL('Product Lookup')) explicitly removes every filter on the 'Product Lookup' table (via ALL + the outer CALCULATE).
- Even if the FILTER iterator introduces any row-specific filtering during predicate evaluation, the ALL wipes it out.
- Result: The measure always returns the true grand-total average across all products (constant scalar, same for every row in the iteration).
- The condition correctly identifies products where ProductPrice > grand average.
- FILTER returns the proper subset of high-price product rows.
- CALCULATE([Total Orders], <that filtered table>) correctly counts the distinct orders linked to those products → shows the expected number.
Why the empty Matrix still shows this difference
The grand-total context (no dimensions) means the outer filter context is the same for both versions. But the internal evaluation inside FILTER(...) is what matters—and that's where the simple aggregator vs. the CALCULATE(... ALL ...) version behaves differently.
Recommended fix / best practice
Don't reference the measure directly in the FILTER predicate when it aggregates the iterated table. Instead, capture the scalar value once (outside the iterator) with a variable:
High Ticket Orders = VAR AvgPrice = [Average Retail Price] // or [Overall Average Price] — either works now RETURN CALCULATE( [Total Orders], FILTER( 'Product Lookup', 'Product Lookup'[ProductPrice] > AvgPrice ) )
(You could also hard-code the comparison with CALCULATE([Average Retail Price], ALL('Product Lookup')) directly in the VAR, or use AVERAGEX(ALL('Product Lookup'), 'Product Lookup'[ProductPrice]).)
I hope this helps. if so please mark it as a solution. kudos are welcome.