DAX and Data Modeling
Written By: Sajagan Thirugnanam
Last Updated on September 23, 2026
To fix a slow measure, first find it with Performance Analyzer in Power BI Desktop, then look at how it spends its time in DAX Studio. The usual causes are an iterator that calls a measure on every row of a large table, FILTER over a whole table inside CALCULATE, the same expression calculated more than once, and a model that is not a clean star schema. Fix the model first when it is the cause, because no rewrite of a measure makes up for it.
This post is about slow DAX. If the whole report is slow, including visuals that use simple measures, start with the slow report troubleshooting guide or the Power BI performance guide.
Why DAX Measures Become Slow
A DAX query in an import model is answered by two engines:
The storage engine (VertiPaq) scans compressed columns. It is fast, and it can run in parallel.
The formula engine handles everything the storage engine cannot, such as complex logic and row-by-row evaluation. It runs on a single thread and is slower.
A measure is slow when it pushes a lot of work into the formula engine, or when it makes the storage engine run many separate scans. Common causes:
Iterators that call a measure for every row of a large table
FILTER over a whole table used as a CALCULATE filter
The same expression calculated several times
Bi-directional or long chains of relationships
High-cardinality columns in filters and relationships
Step 1: Identify the Slow Measure (Performance Analyzer)
Measure before changing anything.
In Power BI Desktop, open the Optimize ribbon and select Performance Analyzer.
Select Start recording, then Refresh visuals.
Expand the slowest visual. Compare DAX query time with Visual display and Other.
If DAX query time dominates, select Copy query, or Run in DAX query view to see the query the visual sends.
If Visual display or Other dominates, the measure is not the problem. Look at the number of visuals on the page instead. Use Export to save the results as a JSON file for comparison after each change.
Step 2: Reduce Iterator Usage (SUMX, FILTER, AVERAGEX)
An iterator is not slow by default. A simple expression over columns of one table is calculated by the storage engine:
This measure is fine, and there is no need to replace it with a calculated column. The SUM vs SUMX post explains why it is also the correct way to write revenue.
Slow Pattern
The expensive pattern is an iterator that calls a measure on every row of a large table:
Each row turns into a filter through context transition, and [Margin] is evaluated once per row. On a sales table with millions of rows, that is millions of evaluations.
Faster Approach
Iterate the smallest table that answers the question, or write the row expression with columns:
When the logic really does need a measure per item, iterate the dimension rather than the fact table:
Rule: keep row expressions to column arithmetic, and iterate as few rows as possible.
Step 3: Avoid Repeated Calculations (Use Variables)
When one expression appears twice in a formula, it can be calculated twice.
Slow Measure
The prior-year CALCULATE appears in both the numerator and the denominator.
Optimized Version
Variables avoid running the same calculation twice, because DAX evaluates a variable once and reuses the result. Name variables carefully: VAR Sales = ... fails in any model with a table called Sales. The DAX variables guide covers the rules.
Step 4: Minimize FILTER() Inside CALCULATE()
Slow Pattern
FILTER walks every row of the Sales table and returns a table that CALCULATE then applies as a filter.
Faster Alternative
A Boolean filter on one column is what the storage engine is built for. KEEPFILTERS keeps any existing Region selection, as the FILTER version did. Leave it out if the measure should always show West whatever the slicer says.
FILTER is still needed when the condition uses a measure, for example FILTER ( VALUES ( 'Date'[Month] ), [Profit] > 0 ). Filter a small column like that, not the fact table.
Step 5: Optimize Data Model Design (Most Important)
Many slow measures are slow because of the model under them. A clean star schema often helps more than rewriting DAX.
Use a star schema: fact tables related to dimension tables. The star vs snowflake post compares the two.
Avoid bi-directional relationships unless a calculation needs one.
Keep relationship chains short.
Remove columns no report uses.
Relate tables on integer keys rather than long text keys.
Step 6: Reduce Cardinality
A column with many distinct values compresses worse and costs more to filter and to relate on. Typical examples:
Transaction IDs
GUIDs
Date-time columns with seconds
Ways to reduce it:
Split a date-time column into a date column and a time column.
Remove unique ID columns that no report uses.
Round or aggregate values upstream when the detail is not needed.
The relationships and cardinality post goes into relationship cardinality specifically.
Step 7: Avoid Complex Nested CALCULATE()
Nesting CALCULATE inside CALCULATE is not expensive in itself. The cost comes from CALCULATE running once per row. That happens every time a measure is referenced inside an iterator, because each measure reference is wrapped in an implicit CALCULATE:
This evaluates [Total Sales] once per customer. On a few thousand customers that is fine. On millions of rows it is not.
To keep it fast:
Iterate dimension tables or
VALUESof a key column, not fact tables.Move shared results into variables before the iterator starts.
Combine several filters into one CALCULATE instead of layering them.
Step 8: Use Measures Instead of Calculated Columns (When Appropriate)
Calculated columns:
Are stored for every row and use memory permanently
Add time to every refresh
Measures:
Are calculated at query time
Add nothing to model size
A calculated column is worth its memory when you slice, filter, group or relate on it. For arithmetic you only ever aggregate, a measure is usually the better choice. The measures vs calculated columns post has the full comparison.
Step 9: Understand ALL vs ALLSELECTED Performance Impact
Inside CALCULATE, ALL and REMOVEFILTERS remove the same filters, so switching between them changes readability, not speed. What matters is how much you remove:
ALL ( Sales )on a fact table clears filters on every related dimension. The engine may then aggregate far more rows.REMOVEFILTERS ( 'Product'[Category] )clears one column and leaves everything else in place.
Remove filters from the narrowest columns that give the right answer. ALLSELECTED is harder to reason about inside iterators, so test measures that use it on their own before combining them. The ALLSELECTED vs ALL post explains the difference in results.
Step 10: Test with DAX Studio (Advanced Optimization)
DAX Studio is a free tool that runs a query against your model and shows where the time goes. Paste in the query from Performance Analyzer, turn on Server Timings, clear the cache, and run it.
Look at:
Total time, split into storage engine and formula engine
The number of storage engine queries
Whether any storage engine query contains a callback to the formula engine
A good result is mostly storage engine time from a small number of queries. High formula engine time, or dozens of storage engine queries for one visual, points to the patterns in Steps 2, 4 and 7. Change one thing at a time and rerun the same query to compare.
DAX Performance Optimization Checklist
Before publishing a report, check:
The model is a star schema
No iterator calls a measure over a fact table
CALCULATE filters are Boolean where possible
Repeated expressions are stored in variables
High-cardinality columns are reduced or removed
Slow visuals were checked in Performance Analyzer
FAQs
Why is my Power BI report slow even with simple visuals?
Check Performance Analyzer first. If DAX query time is high, a measure or the model is the cause, often a measure that iterates a large table or a bi-directional relationship. If Visual display or Other time is high, the page has too many visuals or the visuals are waiting on each other.
Are iterator functions always bad?
No. SUMX over column arithmetic on one table is handled efficiently by the storage engine. Iterators become slow when they call a measure or run complex logic on every row of a large table.
What is the biggest performance improvement in Power BI?
A clean star schema with single-direction relationships and no unused high-cardinality columns. After that, removing per-row measure calls from iterators on fact tables usually gives the biggest gain in DAX.
Sources
Use Performance Analyzer to examine report performance - Microsoft Learn
Avoid using FILTER as a filter argument in DAX - Microsoft Learn
Use variables to improve your DAX formulas - Microsoft Learn
CALCULATE function (DAX) - Microsoft Learn
Related to DAX and Data Modeling