Power BI Lesson 31 – Introduction to DAX | Dataplexa
DAX · Lesson 31

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

📐 Measures
Calculations that respond to filter context — they recalculate every time a slicer is changed, a filter is applied, or a visual cell is rendered. Measures are not stored in the table. They exist in the model and are evaluated on demand.
Total Revenue = SUM(Orders[Revenue])
Defined in: Model (not attached to any table row)
📋 Calculated Columns
New columns added to an existing table at model load time. Calculated once per row during refresh. The result is stored in the model. Like a Power Query custom column, but written in DAX and computed after loading.
Margin = Orders[Revenue] - Orders[Cost]
Defined in: A specific table · stored per row
🗂 Calculated Tables
Entire tables generated by a DAX expression. Computed at refresh time and stored in the model. Used for Calendar tables, role-playing dimension copies, or filtered subsets of existing tables.
UKOrders = FILTER(Orders, Orders[Region]="UK")
Defined in: Model · stored as a full table

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 formula bar — measure definition
Total Revenue = SUM ( Orders [ Revenue ])
Measure name
Function
Table name
Column name
// 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.

Filter Context
The set of filters currently active — from slicers, visual filters, page filters, and cross-filtering between visuals. Filter context is applied to the entire model before a measure evaluates. It determines which rows each table "sees."
Example: SUM(Orders[Revenue]) in different filter contexts
No filter active$4,115
Region slicer = "UK"$1,630
Region = "UK" AND Year = 2024$850
Same formula. Different contexts. Different results. This is correct behaviour.
Row Context
When DAX evaluates a calculated column, it processes each row one at a time. For that row, the "current row" context is active — column references return the value of that specific row. Row context only exists in calculated columns and iterator functions (SUMX, AVERAGEX, etc.).
Calculated column: Margin = Orders[Revenue] - Orders[Cost]
RowRevenueCostMargin
10011,200800400
1002850600250
1003430290140
Each row sees its own Revenue and Cost — not the sum of all rows.

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]))
The same formula behaving differently by context type
CALCULATED COLUMN — row context
OrderIDRevenueQtyRev/Unit
10011,2004300.00
10028500null
10034302215.00
10051,5406256.67
Each row gets its own result · null for zero quantity
MEASURE — filter context
Context (slicer)Rev Per Unit
No filter316.54
Status = Delivered300.00
Tier = Premium280.00
Year = 2023475.00
One formula · four different results · all correct

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))
    )
Five measures in a table visual — filtered by Region slicer
Region Total Revenue Order Count Avg Order Unique Cust % of Total
North$1,6302$815239.6%
South$1,2952$647231.5%
West$1,1901$1,190128.9%
(Blank)$0000%
Total$4,1155$8234100%
DIVIDE returns — for zero-denominator rows · % of Total uses ALLSELECTED so it recalculates relative to whatever is in the slicer

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.