
Picture this: You've just created a calculated column in Power BI that should show each product's percentage of total sales. But instead of getting sensible percentages like 15% or 8%, you're seeing bizarre results — some products showing 100%, others showing impossibly small decimals. Your formula looks right, but the numbers are completely wrong.
This frustrating scenario happens to nearly every DAX beginner, and it stems from a fundamental misunderstanding of how DAX actually evaluates your formulas. The culprit? A confusion between row context and filter context — two completely different ways that DAX processes data that govern every single calculation you'll ever write.
Understanding these contexts isn't just academic theory. It's the difference between formulas that work predictably and formulas that produce mysterious, wrong results. Once you grasp how row context and filter context operate, you'll write DAX with confidence, debug problems faster, and avoid the most common pitfalls that trip up new users.
What you'll learn:
You should be comfortable creating basic calculated columns and measures in Power BI, and familiar with simple DAX functions like SUM, COUNT, and CALCULATE. We'll build from there.
Let's start with row context, which is probably more intuitive if you've worked with Excel formulas.
Row context occurs when DAX processes your formula one row at a time through a table. Think of it like Excel's approach: when you write a formula in cell C2 that references A2 and B2, Excel automatically knows you're talking about the values in row 2. DAX works similarly in calculated columns.
Consider a simple sales table with these columns:
If you create a calculated column called "Revenue" with this formula:
Revenue = Sales[Quantity] * Sales[UnitPrice]
DAX evaluates this formula in row context. For each row, it automatically grabs the Quantity and UnitPrice values from that specific row and multiplies them together. Row 1 gets row 1's values, row 2 gets row 2's values, and so on.
This behavior feels natural because it mirrors how spreadsheets work. But here's where things get interesting: row context only gives you access to columns in the current row. You can't directly reference other rows or aggregate data across the table.
Let's say you want to calculate each product's share of total revenue. Your instinct might be to write:
Revenue Share = Sales[Revenue] / SUM(Sales[Revenue])
But this won't work as expected in a calculated column. While Sales[Revenue] correctly references the current row's revenue value, SUM(Sales[Revenue]) doesn't behave like you'd expect. In row context, aggregate functions like SUM return the value from the current row — not the total across all rows.
This happens because row context doesn't automatically create the filter context needed for aggregation. To get the total revenue, you need to explicitly create that context using functions like ALL or SUMX.
Key insight: Row context gives you column values from the current row, but doesn't automatically aggregate across rows. You need to be explicit about when you want to break out of the current row's scope.
Filter context works completely differently. Instead of processing row by row, filter context determines which rows from your tables should be included in a calculation, then performs aggregations on that filtered set.
Filter context is what makes measures work. When you create a measure like:
Total Revenue = SUM(Sales[Revenue])
This measure doesn't have a specific row to work with. Instead, it looks at whatever filter context exists — which rows are currently "visible" based on slicers, report filters, or the current cell in a matrix — and sums the Revenue column for those rows.
Filter context comes from several sources:
Report filters and slicers: When a user selects "Electronics" in a product category slicer, that creates filter context limiting calculations to electronics products only.
Visual context: In a matrix or table visual, each cell has implicit filter context based on its row and column headers.
Explicit filters: Functions like CALCULATE let you create or modify filter context directly in your formulas.
Let's see this in action. Imagine you have a matrix visual with ProductCategory on rows and Years on columns, showing your Total Revenue measure. When DAX calculates the value for the "Electronics, 2023" cell, the filter context includes:
Your SUM(Sales[Revenue]) measure automatically operates on just the rows that match both conditions.
Here's where filter context gets sophisticated: it propagates through relationships in your data model. If your filter context includes "Electronics" products, and you have a relationship between your Sales table and a Customers table, then measures operating on the Customers table will automatically be filtered to only customers who bought electronics products.
This relationship propagation is incredibly powerful but can also create unexpected results if you don't understand what's happening behind the scenes.
The context type depends entirely on where your DAX formula lives:
Calculated columns always use row context. When you create a calculated column, DAX evaluates your formula once for each row in the table, with access to that row's column values.
Measures always use filter context. When you create a measure, DAX waits until the measure is used in a visual or calculation, then evaluates it based on whatever filter context exists at that moment.
This fundamental difference explains why the same DAX formula can produce completely different results depending on whether it's in a calculated column or a measure.
Let's demonstrate with a concrete example. Suppose you have this sales data:
| ProductName | Revenue |
|---|---|
| Widget A | 1000 |
| Widget B | 1500 |
| Widget C | 500 |
If you create a calculated column with SUM(Sales[Revenue]), you'll get these results:
| ProductName | Revenue | SUM as Column |
|---|---|---|
| Widget A | 1000 | 1000 |
| Widget B | 1500 | 1500 |
| Widget C | 500 | 500 |
Each row shows its own revenue value because row context limits SUM to the current row.
But if you create a measure with the same SUM(Sales[Revenue]) formula and put it in a table visual, you'll see:
| ProductName | Revenue | SUM as Measure |
|---|---|---|
| Widget A | 1000 | 3000 |
| Widget B | 1500 | 3000 |
| Widget C | 500 | 3000 |
Each row shows the total across all products because filter context (modified by the visual) lets the measure aggregate across all visible rows.
Remember: Same formula, different context, completely different behavior. This is why understanding context is absolutely crucial for DAX success.
Now we reach one of DAX's most important and confusing concepts: context transition. This happens when row context automatically converts to filter context under specific circumstances.
Context transition occurs when you use certain DAX functions — particularly CALCULATE and measures — within row context. When this happens, DAX takes the current row's values and converts them into filter conditions.
Let's see this in action. Going back to our revenue share example, this formula will work in a calculated column:
Revenue Share =
DIVIDE(
Sales[Revenue],
CALCULATE(SUM(Sales[Revenue]), ALL(Sales))
)
Here's what happens step by step:
Sales[Revenue] gets the current row's revenue valueCALCULATE(SUM(Sales[Revenue]), ALL(Sales)) triggers context transitionALL(Sales) removes all filters, so SUM operates on the entire tableThe CALCULATE function always triggers context transition when used in row context. This is why it's such a powerful and essential DAX function.
When you reference a measure from within row context, context transition also occurs automatically. This is crucial for understanding how calculated columns that reference measures behave.
Consider this scenario: you have a measure called [Total Sales] that sums sales values, and you want to create a calculated column showing each customer's percentage of total sales:
Customer Share =
DIVIDE(
[Total Sales],
CALCULATE([Total Sales], ALL(Customers))
)
Even though this is a calculated column (row context), the [Total Sales] measure triggers context transition. For each customer row, DAX converts that customer's information into filter context, so the measure calculates just that customer's total sales.
Let's work through some practical examples to solidify these concepts.
You want to create a calculated column showing running totals of sales by date. This requires combining row context with filter context manipulation:
Running Total =
CALCULATE(
SUM(Sales[Amount]),
FILTER(
ALL(Sales),
Sales[Date] <= EARLIER(Sales[Date])
)
)
Breaking this down:
CALCULATE triggers context transitionFILTER creates new filter context for rows where Date is less than or equal to the current row's dateEARLIER reaches back into the original row context to get the current row's date valueYou need to rank products by revenue within each category:
Category Rank =
RANKX(
FILTER(
ALL(Products),
Products[Category] = EARLIER(Products[Category])
),
CALCULATE(SUM(Sales[Revenue])),
,
DESC
)
Here's the logic:
FILTER with EARLIER creates a subset of all products in the same categoryRANKX ranks the current product against others in its categoryCALCULATE(SUM(Sales[Revenue])) expression triggers context transition for each product being rankedSometimes you need different aggregation logic based on current row values:
Conditional Total =
IF(
Products[Category] = "Premium",
CALCULATE(SUM(Sales[Revenue]), ALL(Products), Products[Category] = "Premium"),
CALCULATE(SUM(Sales[Revenue]), ALL(Products))
)
This formula shows total revenue for premium products when the current row is a premium product, otherwise shows total revenue for all products.
Let's practice these concepts with a step-by-step exercise. You'll need a simple dataset with sales transactions.
Step 1: Create Sample Data Create a new Power BI file and enter this data in the Data view:
Sales table:
Step 2: Row Context Example Create a calculated column called "Same Row Sum":
Same Row Sum = SUM(Sales[Amount])
Notice how each row shows its own amount value, not the total across all rows.
Step 3: Context Transition Example Create another calculated column called "Category Total":
Category Total = CALCULATE(SUM(Sales[Amount]), ALL(Sales), Sales[Category] = EARLIER(Sales[Category]))
This should show the total amount for each product's category on every row.
Step 4: Measure Example Create a measure called "Total Sales":
Total Sales = SUM(Sales[Amount])
Add this to a table visual along with Product and Amount. Notice how the measure shows different values based on the visual's filter context.
Step 5: Compare Behaviors Create a matrix with Product on rows and add both your calculated columns and measure. Observe how they behave differently as you add filters or slicers.
Problem: Creating calculated columns that reference measures and getting unexpected results.
Example:
Wrong Share = [Total Sales] / [Total Sales] // Always equals 1
Solution: Understand that measures trigger context transition. Use ALL or other filter manipulation:
Correct Share = [Total Sales] / CALCULATE([Total Sales], ALL(Sales))
Problem: Writing calculated columns expecting SUM or other aggregates to work across rows.
Example:
Wrong Percentage = Sales[Amount] / SUM(Sales[Amount]) // SUM returns current row value
Solution: Use CALCULATE to create proper filter context:
Correct Percentage = DIVIDE(Sales[Amount], CALCULATE(SUM(Sales[Amount]), ALL(Sales)))
Problem: Measures returning unexpected values due to relationships and filter propagation.
Symptom: A customer count measure showing different values than expected when filtered by product.
Solution: Use functions like CROSSFILTER or modify relationships to control propagation, or use ALL/ALLEXCEPT to remove unwanted filters.
When your DAX isn't working as expected:
Identify the context: Is this a calculated column (row context) or measure (filter context)?
Check for context transition: Are you using CALCULATE or referencing measures within row context?
Trace filter propagation: What relationships might be affecting your filter context?
Use debugging measures: Create simple measures that show you what's in your filter context:
Debug Row Count = COUNTROWS(Sales)
Test with ALL: Remove all filters to see if that changes your results:
Debug Total = CALCULATE([Your Measure], ALL())
Row context and filter context represent fundamentally different ways DAX processes your formulas:
The key insight is that the same DAX formula can produce completely different results depending on its context. Calculated columns use row context, measures use filter context, and certain functions trigger the transition between them.
Master these concepts and you'll find DAX much more predictable. You'll understand why your formulas behave the way they do, write more effective calculations, and debug problems faster.
Next steps in your DAX journey:
Remember: every DAX expert started exactly where you are now, confused by context. The difference is they pushed through the initial confusion and practiced until these concepts became second nature. Keep experimenting, keep questioning why your formulas behave as they do, and soon you'll develop the intuitive understanding that separates DAX beginners from DAX masters.
Learning Path: DAX Mastery