Why this session exists
Everyone who opens Power BI hits the same wall: "should this be a calculated column or a measure?" and later, "why does my number change when I click a slicer?" Both walls are the same wall. Once you see that a measure re-runs inside whatever filters surround it, DAX stops being incantations and becomes one repeated move. We build that intuition on Daybreak, in the browser, with the SQL visible - because the generated GROUP BY is filter context wearing its work clothes.
Measures vs calculated columns 6 min live
Every calculation in a BI model runs at one of two moments. A calculated column runs once per row when the data refreshes, and the result is stored in the table - line_revenue = quantity * price is the classic. A measure runs at query time, inside the filters of whoever is looking - Total Revenue = SUM of line_revenue. Rule of thumb, worth the whole session: if it aggregates, it is a measure. Columns are for row-level labels and keys; measures are for every number a human reads on a dashboard.
LiveThe cost of getting it wrong4 min▶
Imagine storing "revenue" as a calculated column on the customers table: each row gets that customer's lifetime total, computed at refresh. It looks fine - until someone adds a month slicer. The column cannot react: it was computed before the slicer existed, so every filter shows the same frozen lifetime numbers. The dashboard is confidently wrong.
- Frozen numbers: columns ignore slicers, page filters, and cross-highlighting - they were computed at refresh, full stop.
- Bloated model: every stored column costs memory on every row. Measures cost nothing until queried.
- Fork risk: a "revenue" column here and a "revenue" measure there will eventually disagree, and the meeting where they disagree is not a fun meeting.
The audit that started with one slicer. A retail team shipped a dashboard where margin was a calculated column summed by region. Legal-entity filters were added later; the frozen column ignored them, and two quarters of board decks carried region margins that no filter had ever actually touched. The fix was one line - rebuild margin as a measure - but rebuilding trust took longer.
Filter context - the one idea that unlocks DAX 7 min live
Here is the secret that makes a matrix visual work: every cell is the same measure, evaluated under that cell's own filters. The Coffee row filters to Coffee, the March column filters to March, the plan slicer filters everything - and [Revenue] re-runs inside each combination. That set of active filters is the filter context. And CALCULATE is simply the function that says: run this measure, but with changed filters - add one, replace one, or ignore one, regardless of what the visual is doing.
Watch it happen. Each bar below is one measure - revenue - evaluated under a different category filter. Now press Show SQL: the GROUP BY you see is the filter context made visible. One declaration, four evaluations.
LiveCALCULATE in plain words3 min▶
CALCULATE is not magic; it is a filter editor. Read it as: "run this measure, but with these filters forced" - whatever the visual, rows, or slicers are doing. Want Coffee revenue shown on every row of a matrix so you can compare each category against it? Force the category filter:
That is the whole trick. Time intelligence (Part 3), "% of total", and "vs all customers" comparisons are all CALCULATE wearing different filter edits. Exact function syntax and the deeper row-context rules stay with MS Learn's five DAX modules - what you own after tonight is the mental model those modules assume.
Time intelligence, and the measures that lie 7 min live
Once filter context clicks, time intelligence is just CALCULATE editing the date filter: MTD stretches it to "month so far", YTD to "year so far", YoY swaps it for "same period last year". All of it leans on the date table you built in b3 - no proper date table, no time intelligence. But some measures need more care than a date filter: semi-additive measures sum happily across some dimensions and lie across others.
Exhibit A: distinct customers. A customer who orders in February and March is one customer, not two - so you may not add monthly customer counts together. Watch the monthly line, then let SQL catch the lie:
SELECT 'sum of 6 monthly distinct counts' AS method,
SUM(monthly_customers) AS customers
FROM (SELECT COUNT(DISTINCT customer_id) AS monthly_customers
FROM orders
GROUP BY strftime('%Y-%m', order_date))
UNION ALL
SELECT 'distinct customers, Jan-Jun as one period',
COUNT(DISTINCT customer_id)
FROM orders;
LiveRatios divide last4 min▶
Same disease, second strain: ratio measures. Average order value is total revenue divided by total orders - computed at the level you are looking at. The wrong move is averaging the monthly AOVs: months with few orders get the same vote as months with many, and the "average" drifts away from the truth. Define AOV once as a division of two measures, and filter context does the leveling for you.
Our playground's Customers and Avg order value measures are exactly these two trap species - a distinct count and a ratio - which is why they are in the dropdown at all. Every time you pick them, the generated SQL recomputes them inside the current filter, never by summing or averaging pre-cooked numbers.
Self-studyThe full semi-additive family3 min read▶
Distinct counts are the gateway case, but the family is bigger, and PL-300 names it explicitly:
- Balances and inventory: bank balance sums across accounts but not across months - you want the closing balance, not January plus February. Daybreak's active subscription count behaves the same way.
- Rates and percentages: churn rate, margin %, conversion - all ratios; all divide last.
- Min/max style measures: "largest order this period" recomputes per period; summing is meaningless.
The test to memorize: "can I add this across every dimension and stay truthful?" If any dimension says no, write the aggregation rule into the measure's definition card - which is exactly what Demo 1 is about.
Define Daybreak's governed measures ★ 12 min · everyone builds
A governed measure is a definition card: name, formula in business words, filters included and excluded, aggregation traps, owner. Tonight we write four of them for Daybreak and test each against the live database. This little document IS the semantic model - the same "one number, one truth" contract the leader track builds in a3, written by the people who compute it.
Revenue - decide what counts. Daybreak orders can be completed, cancelled, or refunded. Does Revenue include refunded orders? Decide, then run the query below and see how much the choice moves the number. Whatever you pick, write it on the card - the decision matters less than the documentation.
Orders and Active Customers. Orders = count of orders in filter context. Active Customers = distinct customers with at least one order - mark it semi-additive on the card: "never sum across time". Test it: Dimension Month · Measure Customers in the playground.
AOV - a ratio of two measures. AOV = [Revenue] / [Orders], divided inside the current filter. Card note: "never average monthly AOVs". Test: Dimension Plan · Measure Avg order value.
Read your four cards back. Name, formula, inclusions, traps, owner. Congratulations - you just wrote a semantic model. In Power BI these cards become DAX measures with descriptions; in Tableau, calculated fields with comments; in a3's language, the metric dictionary.
SELECT ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue_all_orders,
ROUND(SUM(CASE WHEN o.status = 'completed'
THEN oi.quantity * oi.unit_price
ELSE 0 END), 2) AS revenue_completed_only
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id;
The definitions doc outlives the dashboard. Tools get replaced every few years; a one-page measure dictionary survives every migration, because it is the business logic itself. Teams that write cards first port to a new BI tool in weeks. Teams that let each dashboard define its own "revenue" port never - they rebuild, and re-argue every definition in meetings.
Your turn: stress-test the measures ★ 10 min · build your own
Three exercises, rising difficulty: read a chart with a data wrinkle, prove a pitfall with SQL, and write your first CALCULATE. Get the third one right and you have written the pattern behind half of production DAX.
LiveE1 · Units by roast - and the missing roast3 min▶
Roast only applies to coffee - equipment and add-ons carry NULL, which the playground shows as (none). That row is not an error; it is a modeling decision surfacing in a chart. Your measure card should say how NULLs in a dimension are handled and labeled.
LiveE2 · Prove the AOV pitfall yourself4 min▶
Run it: the average of six monthly AOVs vs AOV computed once over the half year. If the numbers differ, months with fewer orders are voting above their weight in the left-hand version. Which one belongs on the exec dashboard?
SELECT ROUND(AVG(monthly_aov), 2) AS avg_of_monthly_aovs,
(SELECT ROUND(SUM(oi.quantity * oi.unit_price) * 1.0
/ COUNT(DISTINCT o.order_id), 2)
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id) AS true_overall_aov
FROM (
SELECT SUM(oi.quantity * oi.unit_price) * 1.0
/ COUNT(DISTINCT o.order_id) AS monthly_aov
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
GROUP BY strftime('%Y-%m', o.order_date)
);
Self-studyE3 · Write the CALCULATE yourself3 min▶
Daybreak's founder wants "Basic-plan revenue" pinned next to total revenue on every page, whatever the slicers say about plan. Write the pseudo-DAX before peeking. Hint: plan lives on the customers table.
Try it yourself - this week ◐ 20-30 min total
- Write definition cards for your team's three core measures: name, formula in business words, what is included and excluded, owner. For each, note the semi-additive traps - can it be summed across time? across customers? If not, write the correct aggregation on the card.
- Find one ratio metric in the wild being averaged wrongly - an average of averages in a spreadsheet, a dashboard averaging monthly percentages, a KPI that mixes levels. You will find one faster than you expect. Compute the divide-last version and note the gap.
- In the playground, pick every measure with Month as the dimension and read the generated SQL until the filter-context story is boring to you.
- Bring to b5: your ugliest real-world chart. Next session is chart choice, and we will fix it properly.
Official sources covered
Tonight was the conceptual core of the DAX and calculations domain, tool-agnostic and testable in the browser. Exact function syntax, click-paths, and the deeper evaluation-context rules stay with the vendors. This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · The real difference between a measure and a calculated column is...
Columns run once per row at refresh and get stored; measures re-run live inside whatever filters surround them. That is why columns freeze when slicers change - and why anything that aggregates should be a measure.
2 · CALCULATE([Revenue], products[category] = "Coffee") does what?
CALCULATE is a filter editor: it re-runs the measure with the filters you specify overridden, while every other filter from the visual and slicers keeps flowing in. Nothing is stored, nothing is deleted.
3 · Why can you not sum January's and February's distinct-customer counts to get "customers so far"?
One loyal customer, two monthly appearances, but still one customer. Distinct counts (and balances, and ratios) refuse naive summing across time - recompute them in the wider filter context instead, like the UNION ALL proof showed.