Power BI Course
Introduction to DAX
DAX — Data Analysis Expressions — is the formula language of Power BI. You use it to create measures that calculate totals, percentages, running sums, and year-over-year comparisons; calculated columns that add computed values to a table; and calculated tables that generate entire datasets from other tables. DAX looks like Excel but behaves completely differently, and the difference is not subtle. This lesson explains what DAX actually is, how it evaluates, and why understanding evaluation context is the single concept that unlocks everything else.
DAX vs Excel vs Power Query M
Most beginners arrive at DAX from Excel or from Power Query. Both comparisons are useful, but both also produce wrong mental models if taken too far. Understanding where each language lives and what it was designed for prevents the most common beginner errors.
| Excel Formulas | Power Query M | DAX | |
|---|---|---|---|
| Runs when | A cell value changes or the sheet recalculates | On data refresh — before the model loads | When a visual is rendered or interacted with — at query time |
| Operates on | Individual cells by row and column address | Tables, columns, and rows — shaping data | Columns of tables in the data model — never individual cells |
| Knows about filters | Only if you use SUMIF / COUNTIF explicitly | No — runs on raw data before the model | Yes — every DAX expression is always aware of the current filter context |
| Key strength | Ad-hoc cell calculations, familiar syntax | Data cleaning, shaping, and transformation | Dynamic aggregations that respond to slicers, filters, and visual interactions |
| Biggest difference from Excel | — | — | DAX has no concept of a specific row or cell. It aggregates over sets of rows defined by context — not by position. |
The Three Things You Build with DAX
DAX Syntax Basics
DAX formulas follow a consistent structure. Understanding the syntax rules prevents the errors that beginners hit in the first hour — missing brackets, wrong quote types, confusing table names with column names.
// DAX syntax rules
// 1. Column references: TableName[ColumnName]
// Always include the table name — unqualified [ColumnName] works
// inside calculated columns only, and is ambiguous in measures
Orders[Revenue] // correct in measures
[Revenue] // works inside a calculated column in Orders
// avoid in measures — unclear which table
// 2. Measure references: just the measure name (no table prefix)
// Measures live in the model, not in a table
Total Revenue // reference an existing measure
[Total Revenue] // square brackets are optional for measures
// 3. Text strings: double quotes only
"United Kingdom" // correct
'United Kingdom' // WRONG — single quotes mean table/column names in DAX
'Orders'[Revenue] // single quotes wrap table names with spaces
// 4. Numbers: no quotes, no commas
1000 // correct
"1000" // this is text, not a number
1,000 // WRONG — comma is the argument separator
// 5. Comments
// Single-line comment
/* Multi-line
comment */
// 6. Line breaks are allowed anywhere — use them for readability
Total Revenue =
SUM(
Orders[Revenue]
)
Evaluation Context — The Most Important Concept in DAX
Every DAX expression is evaluated inside a context — a set of filters that determines which rows of data are included in the calculation. The result of the same measure formula changes depending on the context it is evaluated in. This is not a bug; it is the entire point of DAX. Understanding context is understanding DAX.
| No filter active | $4,115 |
| Region slicer = "UK" | $1,630 |
| Region = "UK" AND Year = 2024 | $850 |
| Row | Revenue | Cost | Margin |
|---|---|---|---|
| 1001 | 1,200 | 800 | 400 |
| 1002 | 850 | 600 | 250 |
| 1003 | 430 | 290 | 140 |
Why Measures Behave Differently From Calculated Columns
The most confusing moment for beginners is writing the same formula as a measure and as a calculated column and getting different results. The reason is context. A measure has no row context by default — it aggregates over all rows visible in the current filter context. A calculated column always has row context — it evaluates once per row.
// Scenario: Understanding the context difference
// As a CALCULATED COLUMN on the Orders table:
Revenue Per Unit = Orders[Revenue] / Orders[Quantity]
// Row context is active — this divides THIS row's Revenue by THIS row's Quantity
// Result per row: 300, null (0 qty), 215, 95, 256.67
// As a MEASURE:
Revenue Per Unit = SUM(Orders[Revenue]) / SUM(Orders[Quantity])
// No row context — cannot reference Orders[Revenue] directly as a scalar
// Must aggregate first: sum all Revenue, sum all Quantity, divide
// Result: 4115 / 13 = 316.5 (grand total)
// Result when filtered to Order 1001: 1200 / 4 = 300
// The column reference [Revenue] in a measure does NOT mean "this row's Revenue"
// It means nothing without an aggregation — DAX will return an error
// This is the #1 beginner mistake: treating measure code like Excel cell references
// WRONG measure (returns error or entire column sum unexpectedly):
Revenue Per Unit = Orders[Revenue] / Orders[Quantity]
// Without SUM(), DAX doesn't know which row's values to use
// CORRECT measure:
Revenue Per Unit = DIVIDE(SUM(Orders[Revenue]), SUM(Orders[Quantity]))
| OrderID | Revenue | Qty | Rev/Unit |
|---|---|---|---|
| 1001 | 1,200 | 4 | 300.00 |
| 1002 | 850 | 0 | null |
| 1003 | 430 | 2 | 215.00 |
| 1005 | 1,540 | 6 | 256.67 |
| Context (slicer) | Rev Per Unit |
|---|---|
| No filter | 316.54 |
| Status = Delivered | 300.00 |
| Tier = Premium | 280.00 |
| Year = 2023 | 475.00 |
Writing Your First Measures
Measures are created in the Data view or Report view by right-clicking a table in the Fields pane and selecting "New Measure." The formula bar accepts a DAX expression. The measure name becomes the field name you drag into visuals.
// The five measures every model needs — write these first
// 1. Total Revenue — aggregates all revenue in current filter context
Total Revenue = SUM(Orders[Revenue])
// 2. Order Count — counts rows (not distinct, all rows)
Order Count = COUNTROWS(Orders)
// 3. Average Order Value — safe division to handle zero denominator
Avg Order Value = DIVIDE(SUM(Orders[Revenue]), COUNTROWS(Orders))
// DIVIDE(numerator, denominator) returns BLANK() if denominator = 0
// Never use / for measures that might divide by zero — use DIVIDE()
// 4. Distinct Customer Count
Unique Customers = DISTINCTCOUNT(Orders[CustomerID])
// 5. % of Total — revenue as a percentage of grand total
// ALLSELECTED removes filters from Orders while keeping slicer context
Revenue % of Total =
DIVIDE(
SUM(Orders[Revenue]),
CALCULATE(SUM(Orders[Revenue]), ALLSELECTED(Orders))
)
| Region | Total Revenue | Order Count | Avg Order | Unique Cust | % of Total |
|---|---|---|---|---|---|
| North | $1,630 | 2 | $815 | 2 | 39.6% |
| South | $1,295 | 2 | $647 | 2 | 31.5% |
| West | $1,190 | 1 | $1,190 | 1 | 28.9% |
| (Blank) | $0 | 0 | — | 0 | 0% |
| Total | $4,115 | 5 | $823 | 4 | 100% |
Measures vs Calculated Columns — The Decision Rule
| Use a Measure when… | Use a Calculated Column when… | |
|---|---|---|
| Needs to respond to slicers | Yes — measures recalculate with every filter change | No — calculated columns are fixed at refresh time |
| Aggregation required | Yes — SUM, COUNT, AVERAGE, DIVIDE all need a measure | No — calculated columns compute row by row, no aggregation |
| Used in a slicer or filter | No — measures cannot be placed in a slicer | Yes — calculated columns appear as fields in slicers and filters |
| Stores data in the model | No — computed on demand, uses no storage | Yes — every row value is stored; adds to model size |
| Example | Total Revenue, % of Total, YoY Growth, Running Total | Revenue Tier label, Margin amount per row, Full Name from First + Last |
Teacher's Note: The beginner instinct is to use calculated columns for everything — they feel familiar because they behave like Excel cells, one value per row. Resist this. The default choice in DAX should always be a measure. Measures are smaller (zero storage), dynamic (respond to slicers), and composable (one measure can call another). Only reach for a calculated column when you genuinely need the result stored per row — for slicing, filtering, or as input to a relationship. If you are unsure, write a measure first. You can always convert it to a calculated column later; the reverse is harder.
Practice
Practice 1 of 3
A DAX measure recalculates every time a slicer is changed because it evaluates within the current ___ context — the set of active filters applied to the model at that moment.
Practice 2 of 3
To safely divide two measures without risking an error when the denominator is zero, you use the DAX function ___ instead of the division operator /.
Practice 3 of 3
A Revenue Tier column that assigns "Gold", "Silver", or "Bronze" based on each row's revenue value should be a ___ column — not a measure — because it computes a fixed label per row and needs to be available as a slicer field.
Lesson Quiz
Quiz 1 of 3
You create a measure Total Revenue = SUM(Orders[Revenue]). A colleague expects this measure to always return the company-wide total regardless of slicers. But when a Region slicer is applied, the measure returns only the revenue for the selected region. Is this a bug?
Quiz 2 of 3
A calculated column formula Margin = Orders[Revenue] - Orders[Cost] works perfectly. You then write an identical measure Margin = Orders[Revenue] - Orders[Cost]. The measure returns an error or wrong result. Why?
Quiz 3 of 3
Which of the following should be a measure rather than a calculated column?
Next up — Lesson 32 covers DAX Syntax in depth, including all operator types, the full function category reference, implicit vs explicit type conversion, and the formatting conventions that make complex DAX expressions readable and maintainable.