Core modeling skill

Module 3 — DAX & Filter Context

DAX becomes much easier when you reason about evaluation context before you reason about syntax. This module explains how row context and filter context work, how CALCULATE changes evaluation, why iterators behave differently from simple aggregators, and how these ideas support time intelligence, semi-additive measures, variables and calculation groups.

Your progress is saved on this browser.
0/160 questions completed
Concept 1

Filter context, row context, CALCULATE & context transition

The most important DAX skill for PL-300 is understanding the context in which an expression is evaluated. Most confusing DAX questions become manageable once you identify the current filter context, whether row context exists, and whether the formula changes either one.

Filter context

Filter context is the set of filters that determines which rows are visible to a calculation. It can come from slicers, visual axes, page/report filters, relationships, drillthrough context, or DAX itself. A measure is always evaluated in the filter context of the cell or visual where it appears.

Example: If a matrix has Product Category on rows and Year on columns, [Total Sales] is evaluated separately for each category-year intersection. You do not need CALCULATE() merely to make the measure respond to those filters.

Row context

Row context means “the current row.” It exists naturally in calculated columns and is created by iterator functions such as SUMX, AVERAGEX and FILTER. Row context by itself does not automatically filter related tables in the same way that filter context does.

What CALCULATE does

CALCULATE() evaluates an expression after modifying filter context. Its first argument is the expression to evaluate, usually a measure; the remaining arguments add, replace or modify filters.

CALCULATE() also performs context transition when it is evaluated in row context. Context transition converts the current row into equivalent filter context. This is why a measure evaluated inside an iterator can return a result for the current entity instead of the whole table.

Context transition: the conversion of existing row context into filter context when CALCULATE (explicitly or implicitly through a measure reference) evaluates an expression.

On the exam, avoid the misconception that CALCULATE() is a generic “make filters work” function. Measures already respect filter context. Use CALCULATE() when you need to change that context or trigger context transition.

DAX
Blue Sales =
CALCULATE (
    [Total Sales],
    Product[Color] = "Blue"
)
  • [Total Sales] is the expression that CALCULATE will reevaluate. It is normally best to build complex measures from reusable base measures like this.
  • Product[Color] = "Blue" is a Boolean filter argument. It creates a filter on the Color column for the calculation.
  • If the current context already contains another filter on Product[Color], this filter normally replaces that same-column filter unless KEEPFILTERS is used.
  • Filters on other columns and related dimensions—such as Date, Region or Customer—remain in effect unless the measure explicitly removes or replaces them.
  • The result is therefore “Total Sales evaluated as if Product Color were Blue, while preserving the rest of the current report context.”
Exam gotcha: A base measure like SUM(Sales[Amount]) already responds to a Region slicer.
Concept 2

DAX filter modifiers

Many analytical measures work by comparing the current filter context with a deliberately modified version of that context. DAX filter-modifier functions let you remove, preserve or combine filters with precision.

REMOVEFILTERS and ALL

REMOVEFILTERS() is the clearest choice when your intention is simply to clear filters. You can remove a filter from one column, several columns or an entire table. If you remove only Product[Category], filters on Date, Region, Customer and other columns remain in effect.

ALL() can also clear filters when used inside CALCULATE(), but it additionally acts as a table function that can return all rows or values. For modern readable DAX, REMOVEFILTERS() often communicates the filter-removal intention more directly.

ALLEXCEPT

ALLEXCEPT(Table, Column...) removes filters from the specified table except the columns you name. For example, it can clear all Customer-table filters while preserving Customer Segment. It does not mean “remove every filter in the whole report except this one column”; unrelated tables can still filter the calculation.

KEEPFILTERS and replacement behavior

A Boolean filter argument in CALCULATE() normally replaces an existing filter on the same column. KEEPFILTERS() changes that behavior so the new filter is intersected with the existing one.

Example: If the current report filter is Product Color = Red and a measure uses CALCULATE([Sales], Product[Color] = "Blue"), the Blue filter replaces the Red filter for that calculation. With KEEPFILTERS(Product[Color] = "Blue"), DAX asks for rows that are both Red and Blue, which normally produces no rows.

These functions are central to percent-of-total, benchmark, market-share and “ignore one slicer but keep everything else” calculations. The safest approach is to remove the smallest necessary filter rather than clearing the entire model context.

DAX
Category % =
DIVIDE (
    [Total Sales],
    CALCULATE (
        [Total Sales],
        REMOVEFILTERS ( Product[Category] )
    )
)
  • The numerator [Total Sales] is evaluated in the full current context, including the category shown on the current matrix row.
  • The denominator uses CALCULATE to reevaluate the same measure after changing one part of that context.
  • REMOVEFILTERS ( Product[Category] ) removes only the Category filter. It does not clear the entire Product table or unrelated dimensions.
  • Because Date, Region, Customer Segment and other filters remain, the denominator is the total for the currently selected slice of the business rather than the grand total of all data.
  • DIVIDE returns the category value divided by that context-aware total and safely handles a zero or blank denominator.
Exam gotcha: REMOVEFILTERS(Product[Category]) does not remove Date, Region or unrelated dimension filters.
Concept 3

Iterators, FILTER, VALUES & aggregation choices

DAX has two broad styles of aggregation: functions that aggregate an existing column directly, and iterator functions that evaluate an expression row by row over a table.

SUM(Sales[Amount]) simply adds the values already stored in one column. By contrast, SUMX(Sales, Sales[Quantity] * Sales[UnitPrice]) first creates row context over Sales, evaluates the multiplication for each row, and then sums those row results.

Common iterators

  • SUMX(Table, Expression) evaluates an expression for each row and sums the results.
  • AVERAGEX evaluates an expression for each row and averages the results.
  • MINX and MAXX evaluate an expression row by row and return the minimum or maximum result.
  • FILTER(Table, Condition) iterates a table and returns only rows where the condition is true. Because it returns a table, it is often used as a filter argument to other DAX functions.

VALUES and changing the grain of a calculation

VALUES(Column) returns the distinct values visible in the current filter context. It is frequently paired with iterators to calculate at an entity grain that differs from the underlying fact table.

Basic statistical aggregations

PL-300 can also test straightforward functions such as AVERAGE, MIN, MAX, COUNT, COUNTROWS and DISTINCTCOUNT. The important choice is the grain being counted or averaged. COUNTROWS(Sales) counts visible rows in the Sales table, while DISTINCTCOUNT(Sales[CustomerID]) counts distinct customer identifiers in the current filter context.

Example: If the Sales table has many transactions per customer, AVERAGE(Sales[Amount]) returns the average transaction amount. AVERAGEX(VALUES(Customer[CustomerID]), [Total Sales]) instead creates one iteration per visible customer, calculates each customer's total sales, and averages those customer totals.

When a measure such as [Total Sales] is referenced inside an iterator, its evaluation uses context transition so the current iterator row can become filter context. This behavior is what makes entity-level patterns such as average sales per customer practical.

DAX
Average Customer Sales =
AVERAGEX (
    VALUES ( Customer[CustomerID] ),
    [Total Sales]
)
  • VALUES ( Customer[CustomerID] ) creates a one-column table containing the distinct customers visible after the current report filters are applied.
  • AVERAGEX iterates that customer table one row at a time, creating row context for each customer.
  • [Total Sales] is a measure reference. Its evaluation for each iterator row uses context transition so the current customer becomes filter context.
  • This produces one total-sales value per visible customer rather than one value per transaction row.
  • AVERAGEX then averages those customer-level totals, which is why the result means “average sales per customer” rather than “average transaction amount.”
Exam gotcha: AVERAGE(Sales[Amount]) and average sales per customer are different questions.
Concept 4

Variables & safe arithmetic

Variables make DAX easier to read, debug and maintain by giving names to intermediate results. They also matter conceptually because a scalar variable is evaluated once in the context where it is defined.

A measure can define one or more variables with VAR and then return the final expression with RETURN. Variables are useful when the same expression would otherwise be repeated, when a calculation has several logical stages, or when you want to inspect intermediate values during debugging.

Evaluation behavior

Once a scalar variable has been evaluated, referencing that variable later does not cause the original expression to be recalculated under a new filter context. This is a subtle but important DAX rule.

Example: If VAR SalesNow = [Total Sales] is defined before a later CALCULATE(), SalesNow contains the already-evaluated scalar. Wrapping that variable in a context-changing CALCULATE() does not magically turn it back into the original measure expression.

Safe division

DIVIDE(Numerator, Denominator [, AlternateResult]) is generally preferred to the / operator when the denominator can be zero or blank. It returns blank by default—or an optional alternate result—instead of forcing you to write a separate defensive IF test.

Variables do not automatically make every measure faster, but avoiding repeated evaluation of expensive identical expressions can help, and the improvement in clarity is often valuable on its own.

DAX
YoY Growth % =
VAR CurrentSales = [Total Sales]
VAR PriorSales = [Prior Year Sales]
RETURN
    DIVIDE ( CurrentSales - PriorSales, PriorSales )
  • VAR CurrentSales = [Total Sales] evaluates the current sales measure once in the context where the variable is declared and stores the resulting scalar.
  • VAR PriorSales = [Prior Year Sales] does the same for the prior-period measure. Giving the two values names makes the final business logic easier to read.
  • RETURN marks the expression that becomes the measure result after all variable declarations have been evaluated.
  • CurrentSales - PriorSales calculates the absolute change, while using PriorSales as the denominator expresses that change relative to the prior-year baseline.
  • DIVIDE handles zero/blank denominators safely. The stored variable values are not automatically recalculated later under a different filter context.
Exam gotcha: Do not assume variables behave like formulas that are recomputed every time their name is referenced.
Concept 5

Date intelligence & period comparisons

Time-intelligence functions modify the current date context so the same base measure can be evaluated for another period—such as prior year, previous month or year to date. The key idea is that the date set changes; the business measure usually stays the same.

A reliable time-intelligence model should use a proper Date table that covers the required range and is related to the relevant fact date. When the Date table is filtered by a visual, functions such as SAMEPERIODLASTYEAR() and DATEADD() return a different set of dates, and CALCULATE() reevaluates the measure using that shifted date set.

Common patterns

  • SAMEPERIODLASTYEAR('Date'[Date]) returns the corresponding date period one year earlier. It is suited to same-period prior-year comparisons.
  • DATEADD('Date'[Date], -1, MONTH) shifts the current date set by a specified interval. It is more flexible when the offset is not simply one year.
  • TOTALYTD([Measure], 'Date'[Date]) evaluates a measure from the beginning of the year through the latest date in the current context. Other dimension filters—such as Product or Region—remain unless explicitly removed.

A typical YoY percentage is (Current - Prior) / Prior. The prior-period value belongs in the denominator because the percentage describes change relative to the earlier baseline.

Example: If a visual is currently filtered to March 2026, a prior-year measure built with SAMEPERIODLASTYEAR evaluates the same sales measure for March 2025. It does not require a separate “2025 sales” column or a hard-coded year filter.

When a fact table contains multiple date roles, remember that time intelligence follows whichever relationship is active for the calculation. Use USERELATIONSHIP() or role-playing date dimensions when the business meaning requires a different date.

DAX
Prior Year Sales =
CALCULATE (
    [Total Sales],
    SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
  • SAMEPERIODLASTYEAR ( 'Date'[Date] ) takes the set of dates currently visible and returns the corresponding set one year earlier.
  • CALCULATE applies that shifted date set as the date filter for the expression.
  • [Total Sales] is then recalculated for the prior-year dates while non-date filters such as Product, Customer or Region continue to apply.
  • The pattern depends on a well-designed Date table and a relationship that allows the Date filter to reach the fact table.
  • Because the base measure is reused, any future correction to [Total Sales] automatically flows into the prior-year measure.
Exam gotcha: For YoY growth, the prior-year value is the normal comparison baseline/denominator.
Concept 6

Semi-additive measures, calculation groups & DAX queries

Not every measure can be summed meaningfully across every dimension. Semi-additive measures are additive across some dimensions but require special treatment across others—most commonly time.

Balances, inventory levels and headcount snapshots are classic examples. It is reasonable to sum the balances of many accounts on the same date, but summing Monday's balance + Tuesday's balance + Wednesday's balance does not produce a meaningful “weekly balance.” Across time, you normally want the opening, closing, minimum, maximum or average snapshot instead.

Closing-balance reasoning

A closing-balance measure should identify the last applicable date in the current reporting period and return the balance for that snapshot. In sparse datasets, the last date in the Date table can differ from the last date that actually has fact data, so robust logic may need to locate the latest data-bearing date.

Calculation groups

A calculation group lets one reusable transformation apply to many measures. Instead of creating separate measures such as Sales YTD, Profit YTD, Quantity YTD, Sales PY, Profit PY and so on, you can define calculation items such as Current, YTD and Prior Year and apply them to whichever measure is currently selected.

SELECTEDMEASURE() represents the measure being transformed by the calculation item. Calculation groups reduce measure proliferation and centralize recurring logic, but they add semantic-model complexity and are unnecessary when only one isolated measure needs a special calculation.

DAX query view

DAX query view is an authoring and inspection surface where you can run DAX queries against the model. The keyword EVALUATE returns a table expression, while functions such as SUMMARIZECOLUMNS() can group and project model results. This is different from defining a model measure: a DAX query returns a result set for inspection or analysis rather than creating a reusable report object.

DAX — last date with data
Closing Balance =
VAR LastDateWithData =
    MAXX (
        FILTER (
            VALUES ( 'Date'[Date] ),
            CALCULATE ( COUNTROWS ( Balance ) ) > 0
        ),
        'Date'[Date]
    )
RETURN
    CALCULATE (
        SUM ( Balance[BalanceAmount] ),
        'Date'[Date] = LastDateWithData
    )
  • VALUES('Date'[Date]) returns the dates currently available in the report's date context.
  • FILTER keeps only dates for which the Balance fact table has at least one row. The inner CALCULATE turns the iterator's current date into filter context so COUNTROWS(Balance) is tested date by date.
  • MAXX returns the latest date from that filtered set, giving the last date that actually contains balance data rather than simply the last date in the calendar.
  • The final CALCULATE evaluates the balance amount only for LastDateWithData, while other current filters such as Account, Product or Region remain in effect.
DAX
// Example calculation item
CALCULATE (
    SELECTEDMEASURE(),
    DATESYTD ( 'Date'[Date] )
)

// DAX query example
EVALUATE
SUMMARIZECOLUMNS (
    Product[Category],
    "Sales", [Total Sales]
)
  • In the calculation-item example, SELECTEDMEASURE() is a placeholder for whichever model measure the calculation group is currently transforming.
  • DATESYTD ( 'Date'[Date] ) returns the year-to-date set of dates for the current date context.
  • CALCULATE reevaluates the selected measure over that YTD date set, allowing the same calculation item to transform Sales, Profit, Quantity and other measures.
  • In the query example, EVALUATE is a DAX query keyword that tells the engine to return the following table expression as the result set.
  • SUMMARIZECOLUMNS groups the result by Product[Category] and adds a named expression "Sales" whose value is produced by [Total Sales]. This query is for inspection/output; it does not create a new model table.
Exam gotcha: A calculation group is useful when many measures need the same transformations; it can be unnecessary for one isolated measure.