📐 Appendix B — DAX Reference & Exam Caveats
- Based on: DAX function reference (Microsoft Learn)
- 📁 ← Back to Home
Syntax and semantics reference for DAX (Data Analysis Expressions) as tested on DP-600. The exam rarely asks you to author complex measures from scratch — it asks you to read a measure and predict its value, or pick the function that produces a required behaviour. Evaluation context is where almost every hard question lives.
Table of contents
- 🧠 1 — The One Concept That Matters: Evaluation Context
- ⚙️ 2 — Measures vs. Calculated Columns
- 🎯 3 — CALCULATE: The Most Important Function
- 🧹 4 — Filter-Removing Functions
- 🔁 5 — Iterators (the “X” Functions)
- 🗓️ 6 — Time Intelligence
- 🪟 7 — Window Functions (Fabric-era DAX)
- 📌 8 — Variables (VAR / RETURN)
- ➗ 9 — Safe Patterns & Handy Functions
- 🧮 10 — SUMMARIZE vs. SUMMARIZECOLUMNS
- ⚠️ 11 — DAX Exam Traps (Rapid Fire)
- 🧾 12 — Reading a Measure Fast (Checklist)
🧠 1 — The One Concept That Matters: Evaluation Context
Every DAX expression is evaluated inside a context. There are two, and they combine.
| Context | Created by | What it does |
|---|---|---|
| Filter context | Slicers, rows/columns of a visual, CALCULATE filters, RLS |
Restricts which rows of the model are visible |
| Row context | Calculated columns, iterators (SUMX, FILTER, …) |
Points at one row at a time; does not filter other tables |
Key Point: Row context does not automatically filter related tables — that only happens through context transition, which is triggered by
CALCULATE(or a measure reference, which wraps in an implicitCALCULATE). This single rule explains most “why is my number wrong?” exam questions.
flowchart TD
A["Row context<br/>(iterating one row)"] -->|"CALCULATE wraps it"| B["Context transition"]
B --> C["Row values become<br/>filter context"]
C --> D["Related tables now filtered"]
A -.->|"no CALCULATE"| E["Related tables NOT filtered"]
⚙️ 2 — Measures vs. Calculated Columns
| Calculated Column | Measure | |
|---|---|---|
| Evaluated | At refresh, row by row | At query time, per visual cell |
| Context | Row context | Filter context |
| Stored? | ✅ Materialized in the model (uses memory) | ❌ Computed on demand |
| Use for | Row-level attributes, relationships, slicer fields | Aggregations, KPIs, ratios |
Exam Caveat: Prefer measures over calculated columns for aggregations. Calculated columns consume memory and are computed at refresh, bloating the model — a calculated column that just sums or averages is almost always the wrong design. Calculated columns are appropriate only when you need a physical, row-level value (e.g. a category to slice by or a relationship key).
🎯 3 — CALCULATE: The Most Important Function
CALCULATE(<expression>, <filter1>, <filter2>, …) evaluates the expression in a modified filter context.
Red Sales =
CALCULATE(
SUM(Sales[Amount]),
Product[Color] = "Red" // adds/overrides filter on Color
)
Rules of CALCULATE:
- Filter arguments override any existing filter on the same column (unless wrapped in
KEEPFILTERS). - It triggers context transition: any row context becomes filter context.
- Filter arguments are Boolean or table expressions; multiple filters combine with AND.
| Modifier | Effect |
|---|---|
ALL(table/col) |
Remove filters (see §4) |
REMOVEFILTERS(...) |
Explicit “remove filters” — clearer than ALL as a modifier |
KEEPFILTERS(...) |
Add a filter without overriding existing context |
ALLEXCEPT(t, cols) |
Remove all filters on t except the listed columns |
USERELATIONSHIP(c1, c2) |
Activate an inactive relationship for this evaluation |
CROSSFILTER(c1, c2, dir) |
Change relationship cross-filter direction |
Exam Caveat: A plain filter argument like
Product[Color]="Red"replaces the existing filter onColor. To narrow the current selection instead of replacing it, wrap it inKEEPFILTERS. Confusing “override” vs. “intersect” is a top-tier DAX trap.
🧹 4 — Filter-Removing Functions
| Function | Removes filters from | Returns a table? | Typical use |
|---|---|---|---|
ALL(Table) |
Entire table | ✅ | % of grand total |
ALL(Table[Col]) |
One column | ✅ | % within other dims |
ALLEXCEPT(Table, Col) |
All columns except listed | ✅ | Keep grouping level |
ALLSELECTED() |
Back to outer/slicer selection | ✅ | % of visible total |
REMOVEFILTERS() |
Same as ALL, but modifier-only intent |
✅ | Clarity inside CALCULATE |
% of Total =
DIVIDE(
SUM(Sales[Amount]),
CALCULATE(SUM(Sales[Amount]), ALL(Sales))
)
Exam Tip:
ALLSELECTED()respects the user’s slicer/outer selection but ignores the current row’s filter — it’s the function for “% of visible total.”ALL()ignores everything, giving “% of grand total.” Questions about subtotal/percentage behaviour hinge on this distinction.
🔁 5 — Iterators (the “X” Functions)
Iterators walk a table row by row (creating row context), evaluate an expression per row, then aggregate.
| Iterator | Aggregates | Note |
|---|---|---|
SUMX(t, expr) |
Sum | Row-level math then sum (e.g. qty × price) |
AVERAGEX(t, expr) |
Average | |
COUNTX(t, expr) |
Count of non-blank | |
MAXX / MINX |
Max / Min | |
RANKX(t, expr, …) |
Rank | Watch ties and ASC/DESC |
CONCATENATEX(t, expr, delim) |
String join | For display lists |
Revenue = SUMX(Sales, Sales[Qty] * Sales[UnitPrice])
Exam Caveat:
SUM(Sales[Qty] * Sales[UnitPrice])is invalid —SUMtakes a single column, not an expression. Row-by-row math requires an iterator (SUMX). Recognising when a scenario needsSUMXvsSUMis a recurring question.
🗓️ 6 — Time Intelligence
Time-intelligence functions require a proper Date table: contiguous dates (no gaps), full years, and Mark as Date Table set.
| Function | Purpose |
|---|---|
TOTALYTD(expr, Date[Date]) |
Year-to-date |
TOTALQTD / TOTALMTD |
Quarter / month-to-date |
SAMEPERIODLASTYEAR(Date[Date]) |
Shift back exactly one year |
DATEADD(Date[Date], -1, MONTH) |
Shift by an interval |
DATESYTD(Date[Date]) |
Table of dates YTD |
PARALLELPERIOD(Date[Date], -1, QUARTER) |
Full prior period |
PREVIOUSMONTH / PREVIOUSYEAR |
Complete prior period as a table |
Sales PY =
CALCULATE(
SUM(Sales[Amount]),
SAMEPERIODLASTYEAR(Date[Date])
)
Exam Caveat: Time intelligence breaks with gaps in the Date table or if it isn’t marked as a date table.
SAMEPERIODLASTYEARneeds a contiguous range; a fact table’s own date column is not a substitute for a dedicated, marked Date dimension.
🪟 7 — Window Functions (Fabric-era DAX)
Newer visual-calc / window functions that appear on the updated exam:
| Function | Purpose |
|---|---|
OFFSET(delta, relation, orderBy) |
Row N positions away (prev/next) |
WINDOW(from, to, …) |
A sliding window of rows |
INDEX(position, relation, orderBy) |
The Nth row (e.g. first/last) |
RANK / ROWNUMBER |
Ranking within a partition |
Exam Tip:
OFFSETandWINDOWare the modern replacement for awkwardEARLIER/FILTERgymnastics for period-over-period and running-total scenarios. If a question offers a cleanOFFSET-based answer against a convoluted alternative, the window function is usually intended.
📌 8 — Variables (VAR / RETURN)
Profit Margin =
VAR Revenue = SUM(Sales[Revenue])
VAR Cost = SUM(Sales[Cost])
RETURN
DIVIDE(Revenue - Cost, Revenue, 0)
- Variables are evaluated once, where they are declared, and are immutable.
- A variable captures the context at its declaration point, not where it’s used.
Exam Caveat: A
VARis evaluated at declaration, in the context that exists there — laterCALCULATEmodifications do not change its value. This “variables are constants” behaviour is deliberately tested: code that expects a variable to react to a modified filter context is wrong.
Exam Tip: Variables improve readability and performance (no re-evaluation), and using them is the safe answer whenever a repeated sub-expression appears in a measure.
➗ 9 — Safe Patterns & Handy Functions
| Function | Why it matters |
|---|---|
DIVIDE(num, den, [alt]) |
Safe division — returns alt (default blank) instead of a divide-by-zero error |
COALESCE(a, b, …) |
First non-blank value |
SELECTEDVALUE(col, [alt]) |
Value if exactly one is in context, else alt — safer than VALUES returning a table |
HASONEVALUE(col) |
Test single selection (for subtotal handling) |
ISINSCOPE(col) |
Detect grouping level in a matrix |
TREATAS(table, col) |
Apply a virtual relationship / filter |
Exam Caveat: Use
DIVIDE()rather than the/operator when the denominator can be zero or blank —/raises an error (orInfinity), whileDIVIDEreturns blank or your supplied alternate. Questions about “handle divide-by-zero gracefully” wantDIVIDE.
🧮 10 — SUMMARIZE vs. SUMMARIZECOLUMNS
SUMMARIZE |
SUMMARIZECOLUMNS |
|
|---|---|---|
| Grouping | ✅ | ✅ |
| Adding measures | ⚠️ Discouraged (context bugs) | ✅ Optimized |
| Auto-removes empty rows | ❌ | ✅ |
| Recommended for | Grouping keys only | Grouping + aggregation |
// Preferred
EVALUATE
SUMMARIZECOLUMNS(
Product[Category],
"Total Sales", SUM(Sales[Amount])
)
Exam Caveat: Do not add measure columns inside
SUMMARIZE— it can produce incorrect results due to context issues. UseSUMMARIZECOLUMNS, orADDCOLUMNS(SUMMARIZE(...), ...), when you need grouping plus calculated values.
⚠️ 11 — DAX Exam Traps (Rapid Fire)
- Row context ≠ filter context. Iterators create row context; only
CALCULATE(or a measure reference) transitions it into a filter. CALCULATEfilters override same-column filters — useKEEPFILTERSto intersect instead.SUM(a * b)is invalid — useSUMXfor row-by-row math.VARis evaluated once at declaration and never reacts to later context changes.ALL= grand total;ALLSELECTED= visible total. Pick by what the % should be “of.”- Time intelligence needs a contiguous, marked Date table — gaps break it.
- Prefer measures over calculated columns for aggregations (memory + refresh cost).
DIVIDEover/for safe divide-by-zero handling.- Don’t add measures inside
SUMMARIZE— useSUMMARIZECOLUMNS. USERELATIONSHIPneeds an existing inactive relationship — it activates, it does not create.SELECTEDVALUEbeatsVALUESwhen you expect a single value — no error on multiple, returns your alternate.KEEPFILTERSintersects;CALCULATE’s bare filter replaces — the same distinction as trap #2, restated because it appears constantly.- Blank propagation: blanks are ignored by
SUM/AVERAGEbut treated as 0 in some arithmetic — watch averages over blanks. - Bi-directional relationships can cause ambiguity and slow queries — single-direction +
CROSSFILTER/TREATASis the exam-preferred pattern.
Exam Tip: For “predict the value” questions, write out the filter context of the target cell first, then apply each
CALCULATE/iterator step in order. Most wrong answers come from forgetting context transition or assuming a filter intersects when it actually overrides.
🧾 12 — Reading a Measure Fast (Checklist)
- What is the filter context of this cell (slicers, row/column headers)?
- Does an iterator introduce row context? Over which table?
- Is there a
CALCULATE(or measure ref) causing context transition? - Do filter arguments override or intersect (
KEEPFILTERS)? - Are any filters removed (
ALL/ALLEXCEPT/ALLSELECTED)? - Are
VARs capturing context at declaration (constant thereafter)? - Any time-intelligence that assumes a valid Date table?