Microsoft Power BI • DAX and Measures

Row vs. Filter Context — Distinguish row context vs filter context conceptually

Understanding the two evaluation contexts that govern every DAX expression is the key to mastering Power BI calculations.

Microsoft Power BI emblem with a dashboard, stacked chart, and database.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

The distinction between row context and filter context did not emerge from thin air; it is a consequence of decades of evolution in analytical query languages and the specific engineering decisions made when Microsoft designed DAX (Data Analysis Expressions). Before DAX, business intelligence tools relied on SQL, MDX, or spreadsheet formulas — each with its own notion of 'which data am I currently looking at?' SQL operates row-by-row in its WHERE clauses and aggregates via GROUP BY, while MDX introduced the concept of multidimensional cubes with tuple-based coordinates. DAX inherited ideas from both worlds and crystallized them into exactly two evaluation contexts, creating a model that is elegant yet initially confusing for newcomers.

1970s • Relational Model & SQL

E. F. Codd's relational algebra introduced the idea of iterating over tuples (rows) and applying predicates — the conceptual ancestor of row context.

1998 • OLAP & MDX

Microsoft Analysis Services launched MDX, where every cell value is determined by the intersection of dimension members — a precursor to filter context.

2009 • PowerPivot & DAX 1.0

DAX debuted as an Excel add-in. Its columnar xVelocity (VertiPaq) engine introduced the dual-context evaluation model that persists today.

2015 • Power BI Desktop GA

Power BI brought DAX to a wider audience, making the row-vs-filter-context distinction the single most important conceptual hurdle for analysts and developers.

2020s • Modern DAX & Optimization

CALCULATE, context transition, and query-folding refinements continue to evolve, but the foundational two-context model remains unchanged.

The central question DAX forces you to answer is deceptively simple: "When this expression evaluates, which row is it looking at, and which subset of the table has been sliced away?" These two independent mechanisms — knowing the current row, and knowing the current filter — interact, overlap, and sometimes transform into each other. Failing to distinguish them is the root cause of most DAX bugs.

SECTION 2

Core Principles & Definitions

Every DAX expression executes within an evaluation context — the combination of environmental information the engine uses to resolve column references and compute results. This evaluation context is composed of two independent components that may be present simultaneously, independently, or not at all. Understanding their orthogonal nature is essential before examining any formula.

1

Row Context

An implicit or explicit pointer to a single row in a table. Created by calculated columns, iterators (SUMX, FILTER, ADDCOLUMNS), and row-level security expressions. Column references without aggregation resolve to the value in that row.
2

Filter Context

A set of active filters that restrict the visible rows across one or more tables. Originates from slicers, report filters, visual axes, and the CALCULATE function. Aggregation functions (SUM, COUNTROWS) operate over whatever rows survive this filter.
3

Context Transition

When CALCULATE (or CALCULATETABLE) is invoked inside a row context, it converts the current row context into an equivalent filter context by adding a filter for every column in the row. This bridge between contexts is powerful but must be used deliberately.
4

No Context ≠ All Data

A measure evaluated with no filter context returns the aggregate over the entire table. Conversely, referencing a column with no row context is an error in a calculated column or is implicitly aggregated in a measure.
✦

KEY TAKEAWAY

Think of row context as a cursor in a database — it points at one record at a time. Think of filter context as a SQL WHERE clause — it narrows the universe of rows before any aggregation occurs. A calculated column has a cursor (row context) but no WHERE clause beyond the whole table; a measure in a visual has a WHERE clause (filter context from slicers) but no cursor unless an iterator creates one.

SECTION 3

Visual Explanation — The Two Contexts in Action

The diagram below illustrates how row context and filter context operate on a simple Sales table. On the left, filter context (shown as a funnel) reduces the table to only those rows matching active slicer selections. On the right, within the surviving rows, an iterator such as SUMX introduces a row context that walks through each row one at a time, evaluating a per-row expression before aggregation.

Row Context vs. Filter Context — Evaluation FlowFull Sales TableProduct Qty AmtLaptop 2 2000Mouse 10 200Laptop 1 1000Keyboard 5 500Mouse 3 60Laptop 4 4000Keyboard 2 200Mouse 7 1408 rows totalFILTERProduct= "Laptop"slicerFilter Context ResultProduct Qty AmtLaptop 2 2000Laptop 1 1000Laptop 4 40003 rows visibleSUMXRow Context→ Row 1Qty×Amt = 2000→ Row 2Qty×Amt = 1000→ Row 3Qty×Amt = 4000Σ = 7000Filter context narrows the table → Iterator creates row context → Per-row expression evaluated → AggregatedSUMX( FILTER( Sales, Sales[Product] = "Laptop" ), Sales[Qty] * Sales[Amt] ) = 7000
The funnel represents filter context reducing 8 rows to 3. The dashed boxes on the right represent the row context created by SUMX as it iterates through each surviving row, computing Qty × Amt before summing the results.

Notice how the two contexts are layered, not alternatives. The filter context acts first, pruning the data. The row context then operates within that already-filtered subset. This layered composition is exactly why a measure like SUMX(Sales, Sales[Qty] * Sales[Price]) yields different results depending on whether a slicer for 'Region = West' is active: the filter context changes the population of rows the iterator sees, even though the row-level expression remains identical.

SECTION 4

How the Engine Resolves Context

Although DAX is not traditionally expressed with mathematical notation, formalizing the evaluation model clarifies how the engine resolves references. Let T be a table with columns c₁, c₂, …, cn. A filter context F is a set of predicates that collectively define a subset T' ⊆ T. A row context R is a binding of each column ci to a specific value vi from a single row r ∈ T'.

FILTER CONTEXT AS SUBSET

T′=r∈T∣p1(r)∧p2(r)∧…∧pk(r)T' = { r ∈ T | p₁(r) ∧ p₂(r) ∧ … ∧ p_k(r) }

Each pi is a predicate contributed by a slicer, report filter, row/column of a matrix visual, or a CALCULATE filter argument. Predicates are conjoined (AND logic) within the same column and across columns.

ROW CONTEXT AS BINDING

R(r)=c1→v1,c2→v2,…,cn→vnwherer=(v1,v2,…,vn)∈T′R(r) = { c₁ → v₁, c₂ → v₂, …, cₙ → vₙ } where r = (v₁, v₂, …, vₙ) ∈ T'

When an iterator such as SUMX scans T', it creates a new row context R(r) for each row r. Column references like Sales[Amount] resolve to the bound value vi of the corresponding column.

CONTEXT TRANSITION (CALCULATE)

CALCULATE(expr)≡exprevaluatedwithF′=F∪c1=v1∧c2=v2∧…∧cn=vnCALCULATE(expr) ≡ expr evaluated with F' = F ∪ { c₁ = v₁ ∧ c₂ = v₂ ∧ … ∧ cₙ = vₙ }

When CALCULATE is called inside a row context R(r), it converts each column binding into a filter predicate and adds it to the existing filter context F, producing a new filter context F'. The row context is consumed; inside the CALCULATE body, only filter context is active unless a new iterator recreates a row context.

⚠

Why Context Transition Matters

A common pitfall: calling a measure inside an iterator. Since measures are implicitly wrapped in CALCULATE, the row context is automatically transitioned into a filter context. If your row context has duplicate rows (i.e., two rows with identical column values), the transitioned filter will match all of them — not just the one the iterator is visiting. This is a classic source of incorrect aggregations.

SECTION 5

Where Each Context Originates

Knowing the theoretical definitions is necessary but not sufficient; a practicing DAX developer must be able to identify which context is active for any given expression. The following diagram maps every common DAX construct to the context it creates, revealing patterns that generalize across hundreds of functions.

Context Origins MapROW CONTEXT"Which single row am I on?"Calculated ColumnAuto-iterates every row of host tableIterators: SUMX, AVERAGEX, MAXX…Explicitly scan a table expressionFILTER( table, predicate )Iterator that returns a table subsetADDCOLUMNS / SELECTCOLUMNSTable constructors that iterate rowsRow-Level Security (RLS)Expression evaluated per row to grant/denyFILTER CONTEXT"Which rows are visible?"Slicers & Report FiltersUser selections applied before evaluationRows / Columns of a MatrixEach cell gets an implicit filter for its coordsCALCULATE( expr, filter₁, … )Adds/replaces filters programmaticallyCross-filter PropagationFilters travel along relationships to related tablesContext Transition (implicit)Row context → filter context via CALCULATETRANSITION
The left panel lists all DAX constructs that produce row context; the right panel lists those that produce filter context. The dashed arrow between RLS/iterators and context transition illustrates how CALCULATE bridges the two.
CharacteristicRow ContextFilter Context
GranularityExactly one row at a timeA set of rows (zero to all)
Default in measuresNot present — must be created by an iteratorAlways present (may be empty = all rows)
Default in calc columnsAutomatically created for each rowNot present beyond initial table load
PropagationDoes not propagate across relationshipsPropagates via model relationships
Modified byNesting another iterator (new row context shadows outer)CALCULATE, CALCULATETABLE, ALL, REMOVEFILTERS

Side-by-side comparison of the two evaluation contexts

SECTION 6

Worked Example — Revenue by Region

Consider a star-schema model with a Sales fact table (OrderID, ProductID, RegionID, Qty, UnitPrice) and a Regions dimension (RegionID, RegionName). We define a measure Total Revenue and explore how it evaluates in a matrix visual with RegionName on rows and a slicer for Year = 2024.

Evaluating Total Revenue in a Matrix Cell

  1. 1

    Step 1 — Define the Measure

    We write: Total Revenue := SUMX( Sales, Sales[Qty] * Sales[UnitPrice] ). This measure is an iterator: SUMX creates a row context over the Sales table and evaluates the per-row expression Qty × UnitPrice, then sums the results.
  2. 2

    Step 2 — Identify the Filter Context

    The user places RegionName on matrix rows and has a slicer set to Year = 2024. When Power BI renders the cell for 'West', the filter context is: Regions[RegionName] = "West" AND Calendar[Year] = 2024. These filters propagate through relationships to the Sales table, leaving only Sales rows that belong to the West region in 2024.
    Filter context: RegionName = "West" ∧ Year = 2024 → Sales table narrowed to, say, 150 rows
  3. 3

    Step 3 — Iterator Creates Row Context

    SUMX now scans the 150 surviving Sales rows. For each row ri, it establishes a row context R(ri) that binds Sales[Qty] and Sales[UnitPrice] to that row's values.
  4. 4

    Step 4 — Per-Row Evaluation

    For each row, the engine computes Sales[Qty] * Sales[UnitPrice]. Suppose the first three rows yield 5 × 20 = 100, 3 × 50 = 150, and 10 × 10 = 100. This computation uses only the row context — the filter context has already done its work by limiting which rows the iterator sees.
  5. 5

    Step 5 — Aggregation

    SUMX accumulates all 150 per-row products: 100 + 150 + 100 + … = total. Suppose the final sum is $47,250. This value is displayed in the matrix cell for West / 2024.
    Total Revenue for West, 2024 = $47,250
💡

Insight

If you replaced SUMX with a simple SUM(Sales[Amount]) (assuming a pre-computed Amount column), the result would be the same — but for a different reason. SUM is an aggregation function that operates in filter context only; it does not create a row context. SUMX, by contrast, explicitly creates row context so it can compute a row-level expression before aggregating. Choose SUMX when the per-row calculation cannot be pre-materialized in a column.

SECTION 7

Common Pitfalls & Comparisons

Most DAX errors stem from confusing which context is active. The table below catalogs frequent mistakes alongside their correct counterparts, emphasizing the mental model shift required.

MistakeWhat Actually HappensCorrect Approach
Using a bare column reference (e.g., Sales[Qty]) in a measure without an iteratorDAX implicitly wraps it in an aggregation (SUM by default in some tools) or throws an error — no row context exists to resolve it.Wrap in an aggregation: SUM(Sales[Qty]) or use SUMX to create explicit row context.
Expecting CALCULATE to 'loop' over rowsCALCULATE modifies filter context, not row context. It does not iterate.Use an iterator (SUMX, FILTER) to create a row-by-row loop, then CALCULATE inside if you need to adjust the filter.
Calling a measure inside SUMX without understanding context transitionThe measure's implicit CALCULATE converts the row context into a filter context, which may match multiple rows if the row is not unique.Ensure the iterated table has a unique key, or use the raw column expressions instead of a measure reference.
Assuming a calculated column has filter context from slicersCalculated columns are evaluated once at model refresh, with a row context but no slicer-driven filter context.Use a measure instead if the result should respond to slicer selections at query time.

Common DAX pitfalls arising from context confusion

✦

KEY TAKEAWAY

In the broader field of programming language theory, DAX's evaluation contexts are analogous to lexical scoping (filter context as the enclosing environment) versus dynamic binding (row context as the current stack frame). Just as a closure captures its enclosing scope, a measure captures the filter context of the visual that calls it; just as a function's local variables shadow outer ones, nested iterators create new row contexts that shadow outer row contexts.

SECTION 8

Connection to Advanced DAX Patterns

Once the two-context model is internalized, you unlock the ability to reason about sophisticated DAX patterns — from time intelligence to virtual relationships. Every advanced pattern is, at its core, a deliberate manipulation of filter context or row context (or both). The table below maps core concepts to their advanced extensions.

Foundational ConceptAdvanced ExtensionContext Mechanism
Filter context from slicersTime intelligence (TOTALYTD, SAMEPERIODLASTYEAR)CALCULATE replaces the date filter with a new set of dates, modifying filter context programmatically.
Row context in SUMXRANKX, PERCENTILEX — statistical iteratorsNested row contexts: the outer iterates the ranking table; the inner (via CALCULATE) evaluates each candidate against a shifted filter.
Context transitionVirtual relationships via TREATAS / USERELATIONSHIPTREATAS injects a column of values as a filter, mimicking a relationship without a physical one in the model.
Removing filter context with ALLPercentage-of-total, market-share calculationsCALCULATE( [Measure], ALL(Dimension) ) removes a filter to compute the denominator across all values of a dimension.

From foundational context concepts to advanced DAX patterns

If you are pursuing optimization or performance tuning with DAX Studio's Server Timings, the dual-context model also explains why certain formulas produce a high number of storage engine queries: each unique combination of filter context values may trigger a separate query against the VertiPaq column store. Understanding this connection between the logical evaluation model and the physical query plan is what separates an intermediate DAX user from an expert.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

A calculated column Sales[LineTotal] = Sales[Qty] * Sales[Price] and a measure Total Sales := SUM(Sales[LineTotal]) are defined. Which evaluation context does the calculated column use to resolve Sales[Qty]? Which context does the measure use to determine the set of rows it aggregates? Explain why a calculated column's result does not change when a user clicks a slicer.
Show answer
The calculated column uses row context: it iterates each row in the Sales table during model refresh, and the reference Sales[Qty] resolves to the Qty value in the current row. The measure Total Sales uses filter context: SUM aggregates Sales[LineTotal] over whichever rows survive the active filters (slicers, visual axes, etc.). A calculated column's result is computed once at refresh time with no slicer-based filter context; it is stored in the model as a static column. Slicers affect only the filter context at query time, which the measure responds to but the pre-computed column does not.

Question 2: BASIC CALCULATION

Given a measure Avg Price := AVERAGEX( Products, Products[ListPrice] ), a Products table with 500 rows, and a slicer set to Category = 'Electronics' (which filters Products to 120 rows), how many times does AVERAGEX's row context activate? What is the table that the iterator scans?
Show answer
AVERAGEX is an iterator that creates a row context for each row of its first argument. The first argument is the Products table, but because the measure is evaluated within a filter context where Category = 'Electronics', only 120 rows survive the filter. Therefore, the row context activates 120 times — once per visible row. AVERAGEX computes Products[ListPrice] for each of the 120 rows (using row context), sums the 120 values, and divides by 120. The filter context restricted the table before the iterator began.

Question 3: INTERMEDIATE

You write a measure: Weighted Avg := SUMX( Sales, Sales[Qty] * RELATED(Products[Price]) ) / SUM(Sales[Qty]). Explain where row context and filter context each play a role. Why is RELATED necessary here, and would the formula work if Products[Price] were used without RELATED?
Show answer
In this measure, SUMX creates a row context over the Sales table. For each Sales row, Sales[Qty] resolves via the row context. However, Products[Price] belongs to a different table. Row context does not propagate across relationships automatically — it only exists on the table being iterated. The RELATED function navigates the many-to-one relationship from Sales to Products and retrieves the Price for the related product row. Without RELATED, DAX cannot resolve Products[Price] in a row context on Sales; it would either error or attempt an implicit aggregation. Meanwhile, filter context (from slicers, visual axes, etc.) determines which Sales rows the SUMX iterates and which rows SUM(Sales[Qty]) aggregates. Both the numerator and denominator are constrained by the same filter context.

Question 4: APPLIED

A matrix visual shows ProductCategory on rows, Year on columns, and a measure Rev % of Total := DIVIDE( [Total Revenue], CALCULATE( [Total Revenue], ALL(Products[Category]) ) ). For the cell at Category = 'Furniture', Year = 2024, describe the filter context for the numerator and the denominator separately. What does ALL(Products[Category]) do to the filter context?
Show answer
For the cell at Category = 'Furniture', Year = 2024, the ambient filter context is {Products[Category] = 'Furniture', Calendar[Year] = 2024}. The numerator [Total Revenue] evaluates under this full filter context, returning revenue for Furniture in 2024. The denominator uses CALCULATE with ALL(Products[Category]), which removes the filter on Products[Category] from the filter context while preserving the Year = 2024 filter. Thus, the denominator returns total revenue across all categories for 2024. DIVIDE then produces the percentage of total category revenue — a classic pattern that works by selectively manipulating filter context. ALL does not create a row context; it modifies the existing filter context by expanding it (removing a restriction) on the specified column.

Question 5: CRITICAL THINKING

Consider a calculated table: RankedProducts = ADDCOLUMNS( Products, "Rank", RANKX( ALL(Products), [Total Revenue] ) ). Inside the RANKX expression, [Total Revenue] is a measure reference. Explain step by step: (a) what context does ADDCOLUMNS provide for each row, (b) what happens when RANKX iterates ALL(Products), (c) how does [Total Revenue] get evaluated inside RANKX — specifically, what context transition occurs, and why does ALL(Products) ensure the ranking covers all products regardless of the outer row context?
Show answer
(a) ADDCOLUMNS iterates the Products table and creates a row context for each product row. The 'Rank' column expression is evaluated once per row. (b) RANKX(ALL(Products), [Total Revenue]) is itself an iterator. It creates a second, inner row context that scans ALL(Products) — the entire Products table regardless of any outer filter or row context. For each candidate product in this inner iteration, [Total Revenue] is evaluated. (c) Because [Total Revenue] is a measure, DAX implicitly wraps it in CALCULATE, triggering context transition: the inner row context (the candidate product's row) is converted into a filter context. This filter context then constrains the Sales table to only that candidate product's sales, and the measure aggregates accordingly. RANKX compares the value produced by the outer row's context transition against all candidates' values to assign a rank. ALL(Products) is essential because, without it, RANKX would only iterate the products visible in the current filter context — in a calculated table there is no slicer filter, but using ALL is defensive and makes the intent explicit: rank across the full product universe.

SUMMARY

Lesson Summary

Summary

Every DAX expression evaluates within a combination of row context and filter context. Row context is a pointer to a single row, created by calculated columns and iterator functions (SUMX, AVERAGEX, FILTER, ADDCOLUMNS). Filter context is a set of active filters that restrict visible rows, originating from slicers, report filters, visual axes, and the CALCULATE function. The two contexts are orthogonal: filter context narrows the data, then row context (if present) walks through each surviving row.

The bridge between them is context transition: when CALCULATE is invoked (explicitly or via a measure reference) inside a row context, it converts each column binding into a filter predicate. Mastering this interplay — knowing when you have row context, when you have filter context, and when one transforms into the other — is the single most important skill for writing correct, efficient DAX. Common pitfalls include referencing columns without aggregation in measures (no row context), expecting CALCULATE to iterate (it modifies filter context, not row context), and ignoring context transition when calling measures inside iterators.

Varsity Tutors • Microsoft Power BI • Row vs. Filter Context