Skip to content

DAX Basics: The Beginner's Guide to Data Analysis Expressions

Learn what DAX is, how measures differ from calculated columns, and write your first five formulas with confidence. A practical starting point for every Power BI analyst.

If you’ve built a Power BI report and hit the wall where drag-and-drop visuals aren’t enough, you’ve met the moment where DAX becomes essential. This guide covers the foundations every analyst needs before moving to advanced patterns — including the mistakes that trip up most beginners and a real scenario you can follow along with.

What is DAX?

DAX (Data Analysis Expressions) is the formula language behind Power BI, Power Pivot for Excel, and Analysis Services Tabular models. It looks similar to Excel formulas, but behaves very differently because it operates over tables and columns rather than cells.

The single most important mental shift: DAX evaluates over a filtered table, not a range of cells. Every measure you write runs in a filter context — the set of rows currently visible based on slicers, visual cross-filtering, and relationships in your data model.

This is why the same measure can return different numbers in different visuals without you changing the formula. A [Total Revenue] measure on a card shows the grand total. The same measure in a matrix grouped by product category shows the total per category — because the matrix applies a filter context that narrows the visible rows to one category at a time.

Measures vs. Calculated Columns

This is the first decision you’ll make for every DAX formula, and it trips up most beginners.

AspectMeasureCalculated Column
When it computesAt query time (on the visual)At data refresh time
StorageNot stored — computed on demandStored in the model (uses memory)
Filter contextDynamic, depends on the visualRow context only (the current row)
Typical useAggregations: SUM, AVERAGE, COUNTRow-level attributes: Price * Qty
Performance impactNegligible (computed per query)Expands model size, slows refresh

Rule of thumb: if the result depends on the visual’s filter context, write a measure. If the result is the same for every row regardless of filtering, write a calculated column.

Common mistake: Overusing calculated columns is the #1 cause of bloated Power BI models. A calculated column is stored in the VertiPaq engine and takes up memory for every row — on a 50-million-row fact table, adding a column can add hundreds of megabytes to your file size. Default to measures unless you genuinely need the value stored per row.

When calculated columns are the right choice

  • Banding / grouping — creating a Price Band column (“Low”, “Medium”, “High”) that you’ll slice on repeatedly
  • Key columns for relationships — concatenating fields to create a unique key
  • Static attributes — values that never change based on filter context (e.g. Customer Tier based on lifetime spend)

If you find yourself writing CALCULATE inside a calculated column to reference another table, you probably want a measure instead.

Real scenario: building a sales report

Let’s walk through a realistic example. Assume a Sales table with columns Revenue, Quantity, UnitCost, and OrderDate, plus a Products dimension table linked by ProductKey.

Total Revenue:

Total Revenue = SUM(Sales[Revenue])

Total Quantity:

Total Quantity = SUM(Sales[Quantity])

Average Order Value:

Avg Order Value = DIVIDE([Total Revenue], [Total Quantity])

Notice we use DIVIDE instead of /. DIVIDE handles divide-by-zero errors gracefully and returns BLANK instead of an error — always prefer it. The / operator will throw “Cannot divide by zero” if the denominator is zero or BLANK, which breaks your entire visual.

Total Cost (calculated column referenced by a measure):

Total Cost = SUMX(Sales, Sales[Quantity] * Sales[UnitCost])

SUMX is an iterator — it walks row by row, computes the expression, then sums the results. Iterators (SUMX, AVERAGEX, MINX, etc.) are essential when a calculation needs row-level arithmetic before aggregation.

Why not make Quantity * UnitCost a calculated column? You could — but if UnitCost changes frequently, a measure recomputes on every refresh without bloating the model. For large fact tables, the iterator pattern is more memory-efficient.

Gross Margin %:

Gross Margin % =
DIVIDE(
    [Total Revenue] - [Total Cost],
    [Total Revenue]
)

Format this as a percentage in the Modeling ribbon. Now every visual showing margin respects whatever filters are applied — by product, by region, by date.

What happens when a user clicks a slicer

When someone selects “Electronics” in a category slicer:

  1. The slicer filters Products[Category] = "Electronics"
  2. The filter flows through the one-to-many relationship to Sales
  3. [Total Revenue] now sums only the rows where the product is in Electronics
  4. [Gross Margin %] recomputes using the filtered totals — no formula change needed

This is the power of filter context: one measure, infinite views.

Essential functions to learn first

CategoryFunctionsWhy you need them
AggregationSUM, AVERAGE, MIN, MAX, COUNTROWSThe basics — every report starts here
IteratorsSUMX, AVERAGEX, FILTERRow-level math before aggregation
LogicalIF, SWITCH, COALESCEConditional logic, avoid nested IFs
Filter manipulationCALCULATE, ALL, ALLEXCEPTModify filter context — the gateway to advanced DAX
Time intelligenceTOTALYTD, SAMEPERIODLASTYEAR, DATEADDDate-based calculations
Error handlingIFERROR, ISBLANK, COALESCEGraceful handling of missing data

If you learn only one function deeply, make it CALCULATE — it’s the gateway to every advanced pattern. See our dedicated CALCULATE guide for the deep dive.

Three common beginner mistakes (and how to fix them)

1. Referencing a column where a measure is needed

// Wrong — returns the column reference, not a value
Total = Sales[Revenue]

// Right
Total = SUM(Sales[Revenue])

A bare column reference in a measure doesn’t aggregate — it returns whatever the current row context provides, which in a visual is often BLANK. Always wrap column references in an aggregation function.

2. Forgetting that filters flow through relationships

A slicer on Products[Category] filters Sales only if the relationship direction allows it. One-to-many relationships filter from the “one” side (dimension) to the “many” side (fact). If your slicer isn’t working:

  • Open Model View and check the relationship arrow direction
  • Confirm the cardinality is 1:* (one-to-many), not 1:1 or *:*
  • Verify the join columns contain matching values (no data type mismatch)

3. Using IF when SWITCH is clearer

// Hard to read — nested IFs become unreadable past 3 levels
Bucket = IF(Sales[Qty] < 10, "Small", IF(Sales[Qty] < 100, "Medium", IF(Sales[Qty] < 1000, "Large", "Enterprise")))

// Better — SWITCH with TRUE() is flat and readable
Bucket =
SWITCH(
    TRUE(),
    Sales[Qty] < 10, "Small",
    Sales[Qty] < 100, "Medium",
    Sales[Qty] < 1000, "Large",
    "Enterprise"
)

SWITCH also performs slightly better than nested IF because the engine evaluates it as a single operation rather than a chain.

How to debug DAX when things go wrong

DAX errors are notoriously unhelpful. Here’s a debugging workflow that works:

  1. Check data types first. “A table of multiple values was supplied” almost always means a column that should be a number is stored as text. Go to Power Query and verify data types before the data loads.

  2. Use DAX Studio. Download DAX Studio (free) and connect to your Power BI model. It shows the actual query generated by your visuals, lets you test measures in isolation, and has a server-timing feature that reveals which part of your DAX is slow.

  3. Break complex measures into parts. If a measure returns wrong numbers, create intermediate measures and check each one:

// Instead of debugging this all at once:
Margin % = DIVIDE(SUMX(Sales, Sales[Revenue] - Sales[UnitCost] * Sales[Quantity]), SUM(Sales[Revenue]))

// Break it into steps:
_Step1 Cost = SUMX(Sales, Sales[UnitCost] * Sales[Quantity])
_Step2 Revenue = SUM(Sales[Revenue])
_Step3 Margin = [_Step2 Revenue] - [_Step1 Cost]
Margin % = DIVIDE([_Step3 Margin], [_Step2 Revenue])

The _ prefix marks these as internal — delete them once the final measure works.

  1. Test in a matrix, not a card. A card shows one number with no context. A matrix grouped by a dimension lets you see if the measure works correctly per row and if the grand total matches.

Performance tips for larger models

  • Prefer measures over calculated columns — measures compute on demand and don’t bloat file size
  • Use DIVIDE instead of / — it’s safer and the engine optimizes it
  • Avoid FILTER inside CALCULATE for simple column filtersCALCULATE([Revenue], Products[Color] = "Red") is faster than CALCULATE([Revenue], FILTER(Products, Products[Color] = "Red")) because the first uses the column’s dictionary directly
  • Remove unused columns — every column in your model is loaded into memory, even if no visual uses it

For a full deep dive on performance, see our Power BI performance optimization guide.

FAQ

Should I learn DAX or Power Query first? Power Query first — clean data prevents 80% of DAX problems. If your data types are wrong or your model isn’t structured properly, no amount of DAX will save you. See our Power Query getting started guide.

Is DAX like Excel formulas? The syntax looks similar, but the mental model is completely different. Excel formulas operate on cells; DAX operates on filtered tables. If you try to write DAX like Excel, you’ll get wrong results that look plausible. Invest 2 hours in understanding filter context and you’ll save weeks of debugging.

How long does it take to learn DAX? Most analysts can write useful measures within a day. Becoming fluent with CALCULATE, context transition, and time intelligence takes 2-4 weeks of regular use. Advanced patterns (window functions, calculation groups) are a 3-6 month journey.

Can I use ChatGPT or Claude to write DAX? Yes, but verify every formula. AI tools frequently produce DAX that “looks right” but has subtle context transition bugs. The debugging steps above will help you catch these. Always test AI-generated DAX in DAX Studio before trusting it in production.

What’s next

You now have the mental model: measures compute in filter context, calculated columns compute in row context, and iterators let you do row-level math before aggregating. The next leap is learning how to modify the filter context — which is exactly what CALCULATE does.

Continue with CALCULATE: The Most Important DAX Function and Time Intelligence Patterns to round out your foundation. If you hit a wall, our DAX troubleshooting guide walks through the most common errors and their fixes.