DAX Fundamentals and Measure Basics
DAX Fundamentals and Measure Basics
DAX (Data Analysis Expressions) is the formula language of Power BI. It is a functional language designed for data modeling and analysis. Unlike Excel formulas, which operate on individual cells, DAX operates on columns and tables. This distinction is fundamental: DAX formulas always reference entire columns or tables, never individual cells.
Measures vs. Calculated Columns
Power BI supports two types of DAX calculations: calculated columns and measures. Understanding the difference is critical.
A calculated column is computed row by row when the data is loaded. The result is stored in the data model, consuming memory. Calculated columns are useful for categorization, grouping, or any value that should be available as a slicer or axis.
A measure is computed at query time, in the context of the visual's filters and slicers. The result is not stored; it is calculated on the fly. Measures are the primary tool for building interactive dashboards because they respond dynamically to user interactions.
// Calculated column: profit per row (stored in the model)
Profit = Sales[Revenue] - Sales[Cost]
// Measure: total profit (computed at query time)
Total Profit = SUM(Sales[Revenue]) - SUM(Sales[Cost])
The rule of thumb: use a calculated column when you need a value for grouping, filtering, or as an axis. Use a measure when you need an aggregated value that responds to slicers and filters.
Filter Context and Row Context
DAX has two evaluation contexts that govern how formulas are computed:
Filter context — the set of filters applied to the data by slicers, visual-level filters, report-level filters, and the visual's rows and columns. Every cell in a matrix or every point in a chart has its own filter context.
Row context — the current row being evaluated in a calculated column or an iterator function. Row context exists when DAX is processing a specific row.
The interaction between these two contexts is the source of both DAX's power and its complexity. A measure like SUM(Sales[Revenue]) computes the sum of revenue in the current filter context. If a slicer filters to the West region, the measure sums only West region revenue.
Basic Measure Syntax
// Simple aggregation
Total Sales = SUM(Sales[Revenue])
// Average
Average Order Value = AVERAGE(Sales[OrderValue])
// Count
Order Count = COUNT(Sales[OrderID])
// Distinct count
Customer Count = DISTINCTCOUNT(Sales[CustomerID])
// Min and Max
Min Order = MIN(Sales[OrderValue])
Max Order = MAX(Sales[OrderValue])
The CALCULATE Function
CALCULATE is the most important function in DAX. It evaluates an expression in a modified filter context. You can add filters, remove filters, or replace filters entirely.
// Total sales in the West region
West Sales = CALCULATE(SUM(Sales[Revenue]), Sales[Region] = "West")
// Total sales ignoring the region filter
All Region Sales = CALCULATE(SUM(Sales[Revenue]), ALL(Sales[Region]))
// Total sales for 2024
Sales 2024 = CALCULATE(SUM(Sales[Revenue]), Sales[Year] = 2024)
CALCULATE works by taking the existing filter context and applying the new filters you specify. If a slicer has selected "West" and you write CALCULATE(SUM(Sales[Revenue]), Sales[Region] = "East"), the result is East region sales — the new filter overrides the slicer.
Best Practices
- Always use measures for aggregations. Calculated columns consume memory and do not respond to slicers.
- Name measures clearly.
Total Salesis better thanMeasure1. - Use variables for complex measures. Variables improve readability and performance.
- Avoid nested CALCULATE when possible. Each CALCULATE adds a layer of filter context modification.
- Test measures in a matrix visual. A matrix with the relevant dimensions makes it easy to verify that a measure computes correctly in different filter contexts.
CALCULATE and Filter Functions
CALCULATE and Filter Functions
CALCULATE is the gateway to advanced DAX. It modifies the filter context in which an expression is evaluated. Mastering CALCULATE and its companion filter functions unlocks the full analytical power of Power BI.
CALCULATE Syntax
CALCULATE(<expression>, <filter1>, <filter2>, ...)
The expression is evaluated in the modified filter context. Each filter argument is a boolean expression or a table expression that defines the new filter context.
// Filter with a simple condition
West Sales = CALCULATE(SUM(Sales[Revenue]), Sales[Region] = "West")
// Filter with multiple conditions
West 2024 Sales = CALCULATE(
SUM(Sales[Revenue]),
Sales[Region] = "West",
Sales[Year] = 2024
)
// Filter with a table expression
High Value Sales = CALCULATE(
SUM(Sales[Revenue]),
FILTER(Sales, Sales[OrderValue] > 1000)
)
The FILTER Function
FILTER returns a table that has been filtered by a boolean expression. It is an iterator — it evaluates the expression for each row in the table and returns only the rows where the expression is true.
// Sales from orders over $1000
High Value Sales = CALCULATE(
SUM(Sales[Revenue]),
FILTER(Sales, Sales[OrderValue] > 1000)
)
// Sales from customers in the VIP segment
VIP Sales = CALCULATE(
SUM(Sales[Revenue]),
FILTER(Customers, Customers[Segment] = "VIP")
)
FILTER is necessary when the filter condition is more complex than a simple equality. For simple conditions, use the direct syntax (Sales[Region] = "West") because it is more efficient.
The ALL Function
ALL removes filters from a column, a table, or an entire table. It is the primary tool for computing percentages of total and for ignoring slicer selections.
// Total sales ignoring all filters on the Sales table
Total Sales All = CALCULATE(SUM(Sales[Revenue]), ALL(Sales))
// Total sales ignoring only the region filter
Total Sales All Regions = CALCULATE(SUM(Sales[Revenue]), ALL(Sales[Region]))
// Percent of total sales
Sales % of Total = DIVIDE(SUM(Sales[Revenue]), CALCULATE(SUM(Sales[Revenue]), ALL(Sales)))
ALL is essential for denominator calculations. Without it, a percentage measure would always return 100% because the numerator and denominator would have the same filter context.
The ALLEXCEPT and ALLSELECTED Functions
ALLEXCEPT removes filters from all columns except the ones you specify. ALLSELECTED removes filters from the current visual but respects slicer selections.
// Total sales by region, ignoring other filters
Region Total = CALCULATE(SUM(Sales[Revenue]), ALLEXCEPT(Sales, Sales[Region]))
// Percent of region total
% of Region = DIVIDE(SUM(Sales[Revenue]), CALCULATE(SUM(Sales[Revenue]), ALLEXCEPT(Sales, Sales[Region])))
// Percent of selected total
% of Selected = DIVIDE(SUM(Sales[Revenue]), CALCULATE(SUM(Sales[Revenue]), ALLSELECTED(Sales)))
The KEEPFILTERS Function
By default, CALCULATE replaces existing filters. KEEPFILTERS preserves existing filters and intersects them with the new filters.
// Sales in the West region, but only if the slicer has selected West
West Sales Keep = CALCULATE(SUM(Sales[Revenue]), KEEPFILTERS(Sales[Region] = "West"))
KEEPFILTERS is useful when you want to add a filter without overriding the user's slicer selection.
Practical Example: Market Share Analysis
Suppose you want to compute each product's market share — its sales as a percentage of total sales in its category.
// Total sales for the product's category
Category Sales = CALCULATE(
SUM(Sales[Revenue]),
ALLEXCEPT(Products, Products[Category])
)
// Market share
Market Share = DIVIDE(SUM(Sales[Revenue]), Category Sales)
The ALLEXCEPT removes all filters from the Products table except the category. This gives the total sales for the product's category, regardless of what other filters are applied. Dividing the product's sales by the category total gives the market share.
Common Pitfalls
- Using CALCULATE without a filter.
CALCULATE(SUM(Sales[Revenue]))is equivalent toSUM(Sales[Revenue]). The filter arguments are what make CALCULATE useful. - Forgetting ALL in denominator calculations. Without ALL, the denominator equals the numerator, and the percentage is always 100%.
- Using FILTER for simple conditions.
FILTER(Sales, Sales[Region] = "West")is less efficient thanSales[Region] = "West". - Confusing ALL with ALLEXCEPT. ALL removes all filters. ALLEXCEPT removes all filters except the specified columns.
- Ignoring filter context in measures. A measure without CALCULATE uses the existing filter context. This is correct for simple aggregations but insufficient for advanced analysis.
Time Intelligence Functions
Time Intelligence Functions
Time intelligence is one of the most common requirements in business dashboards. Stakeholders want to compare this year to last year, compute year-to-date totals, and analyze trends over time. DAX provides a suite of time intelligence functions that make these calculations straightforward — provided you have a proper date table.
The Date Table Requirement
Time intelligence functions require a continuous date table with no gaps. The table must have a date column with one row per day, covering the full range of dates in your data. Without a date table, functions like SAMEPERIODLASTYEAR and DATESYTD return incorrect results.
// Create a date table in DAX
DateTable =
ADDCOLUMNS(
CALENDAR(MIN(Sales[OrderDate]), MAX(Sales[OrderDate])),
"Year", YEAR([Date]),
"Month", MONTH([Date]),
"MonthName", FORMAT([Date], "MMMM"),
"Quarter", QUARTER([Date]),
"DayOfWeek", WEEKDAY([Date])
)
Mark the table as a date table in Power BI (Table tools > Mark as date table). This enables the built-in time intelligence features and ensures correct behavior.
Year-to-Date, Quarter-to-Date, and Month-to-Date
// Year-to-date sales
Sales YTD = TOTALYTD(SUM(Sales[Revenue]), DateTable[Date])
// Quarter-to-date sales
Sales QTD = TOTALQTD(SUM(Sales[Revenue]), DateTable[Date])
// Month-to-date sales
Sales MTD = TOTALMTD(SUM(Sales[Revenue]), DateTable[Date])
These functions compute the cumulative total from the start of the period to the current date in the filter context. If the filter context is June 2024, Sales YTD returns the sum of sales from January 1, 2024 to June 30, 2024.
Prior Period Comparisons
// Sales in the same period last year
Sales PY = CALCULATE(SUM(Sales[Revenue]), SAMEPERIODLASTYEAR(DateTable[Date]))
// Year-over-year growth
YoY Growth = DIVIDE(SUM(Sales[Revenue]) - Sales PY, Sales PY)
// Sales in the previous month
Sales PM = CALCULATE(SUM(Sales[Revenue]), PREVIOUSMONTH(DateTable[Date]))
// Sales in the next month
Sales NM = CALCULATE(SUM(Sales[Revenue]), NEXTMONTH(DateTable[Date]))
SAMEPERIODLASTYEAR returns the same date range shifted back one year. If the filter context is June 2024, it returns June 2023. This is the most common comparison in business dashboards.
Rolling Averages and Moving Totals
// 3-month rolling average of sales
3M Avg Sales =
AVERAGEX(
DATESINPERIOD(DateTable[Date], MAX(DateTable[Date]), -3, MONTH),
SUM(Sales[Revenue])
)
// 12-month rolling total of sales
12M Total Sales =
CALCULATE(
SUM(Sales[Revenue]),
DATESINPERIOD(DateTable[Date], MAX(DateTable[Date]), -12, MONTH)
)
Rolling averages smooth out short-term fluctuations and reveal underlying trends. The 3-month rolling average computes the average monthly sales over the last 3 months for each point in the visualization.
Custom Date Ranges with DATEADD
DATEADD shifts a date range by a specified number of intervals. It is more flexible than SAMEPERIODLASTYEAR because you can specify the interval and the number of periods.
// Sales 2 years ago
Sales 2YA = CALCULATE(SUM(Sales[Revenue]), DATEADD(DateTable[Date], -2, YEAR))
// Sales 6 months ago
Sales 6MA = CALCULATE(SUM(Sales[Revenue]), DATEADD(DateTable[Date], -6, MONTH))
// Sales 30 days ago
Sales 30DA = CALCULATE(SUM(Sales[Revenue]), DATEADD(DateTable[Date], -30, DAY))
Practical Example: Executive Summary Dashboard
An executive summary dashboard typically includes:
- Current period sales:
SUM(Sales[Revenue]) - Prior period sales:
CALCULATE(SUM(Sales[Revenue]), SAMEPERIODLASTYEAR(DateTable[Date])) - Year-over-year growth:
DIVIDE([Current Sales] - [Prior Sales], [Prior Sales]) - Year-to-date sales:
TOTALYTD(SUM(Sales[Revenue]), DateTable[Date]) - Year-to-date growth:
DIVIDE([YTD Sales] - [YTD Sales PY], [YTD Sales PY])
Each measure builds on the previous ones. The time intelligence functions handle the date logic. The DIVIDE function handles the percentage calculation. Together they provide a complete picture of performance vs. plan and vs. prior year.
Common Pitfalls
- Not using a date table. Time intelligence functions return incorrect results without a continuous date table.
- Using SAMEPERIODLASTYEAR with a non-date column. The function requires a date column.
- Forgetting to mark the date table. Power BI needs to know which table is the date table.
- Mixing time intelligence functions with manual date filters. Use one or the other, not both.
- Ignoring leap years. SAMEPERIODLASTYEAR handles leap years correctly, but manual date arithmetic may not.
Iterator Functions and Advanced Patterns
Iterator Functions and Advanced Patterns
Iterator functions in DAX perform a calculation for each row in a table and then aggregate the results. They are essential for calculations that cannot be expressed as a simple aggregation. The most common iterators are SUMX, AVERAGEX, MINX, MAXX, and COUNTX.
How Iterators Work
An iterator function takes a table and an expression. It evaluates the expression for each row in the table, then aggregates the results using the specified aggregation function.
// SUMX: sum of revenue * quantity for each row
Total Revenue = SUMX(Sales, Sales[Revenue] * Sales[Quantity])
// AVERAGEX: average order value
Avg Order Value = AVERAGEX(Sales, Sales[OrderValue])
// COUNTX: count of rows where quantity > 10
High Qty Orders = COUNTX(FILTER(Sales, Sales[Quantity] > 10), Sales[OrderID])
The key difference between SUM(Sales[Revenue]) and SUMX(Sales, Sales[Revenue]) is that the iterator can perform a calculation for each row before aggregating. This is necessary when the calculation involves multiple columns or a complex expression.
When to Use Iterators
Use an iterator when:
- The calculation involves multiple columns (e.g., revenue = price * quantity).
- The calculation requires a filter that depends on row-level values.
- The aggregation is not a simple sum, average, or count.
// Profit = (revenue - cost) for each row, then sum
Total Profit = SUMX(Sales, Sales[Revenue] - Sales[Cost])
// Weighted average price
Weighted Avg Price =
DIVIDE(
SUMX(Sales, Sales[Price] * Sales[Quantity]),
SUM(Sales[Quantity])
)
// Count of orders with value > $1000
High Value Orders = COUNTX(FILTER(Sales, Sales[OrderValue] > 1000), Sales[OrderID])
Variables in DAX
Variables improve readability and performance in complex measures. A variable stores the result of an expression and can be referenced multiple times.
// Measure using variables
YoY Growth % =
VAR CurrentSales = SUM(Sales[Revenue])
VAR PriorSales = CALCULATE(SUM(Sales[Revenue]), SAMEPERIODLASTYEAR(DateTable[Date]))
RETURN
DIVIDE(CurrentSales - PriorSales, PriorSales)
Variables are evaluated once and reused. This is more efficient than repeating the same calculation multiple times. It also makes the measure easier to read and debug.
The SWITCH Function
SWITCH evaluates an expression against a list of values and returns the corresponding result. It is more readable than a nested IF chain.
// Categorize sales performance
Performance =
SWITCH(
TRUE(),
[YoY Growth %] > 0.1, "Strong Growth",
[YoY Growth %] > 0, "Moderate Growth",
[YoY Growth %] > -0.1, "Slight Decline",
"Significant Decline"
)
```n
The `TRUE()` pattern evaluates each condition in order and returns the first match. This is the standard pattern for `SWITCH` when the conditions are not simple equality checks.
### Practical Example: Customer Lifetime Value
Customer lifetime value (CLV) is a common metric that requires an iterator. It is the average revenue per customer multiplied by the average customer lifespan.
```dax
// Average revenue per customer
Avg Revenue Per Customer =
AVERAGEX(
VALUES(Sales[CustomerID]),
CALCULATE(SUM(Sales[Revenue]))
)
// Average customer lifespan in months
Avg Lifespan =
AVERAGEX(
VALUES(Sales[CustomerID]),
DATEDIFF(
MIN(Sales[OrderDate]),
MAX(Sales[OrderDate]),
MONTH
)
)
// Customer lifetime value
CLV = [Avg Revenue Per Customer] * [Avg Lifespan]
The VALUES function returns a table of unique customer IDs in the current filter context. The iterator computes the average revenue and average lifespan for each customer, then averages across customers.
Common Pitfalls
- Using iterators when a simple aggregation suffices.
SUMX(Sales, Sales[Revenue])is less efficient thanSUM(Sales[Revenue]). - Forgetting the table argument. An iterator requires a table as its first argument.
- Using variables without RETURN. A measure with variables must end with a RETURN statement.
- Nesting iterators excessively. Each iterator adds computational cost. Use the simplest iterator that solves the problem.
- Ignoring filter context in iterators. Iterators respect the filter context. Use CALCULATE inside the iterator to modify it.
Practice Problems
Create a DAX measure that computes year-over-year growth as a percentage. The data has a Sales table with Revenue and OrderDate columns, and a DateTable with a Date column.
Solution
YoY Growth % =
VAR CurrentSales = SUM(Sales[Revenue])
VAR PriorSales = CALCULATE(SUM(Sales[Revenue]), SAMEPERIODLASTYEAR(DateTable[Date]))
RETURN
DIVIDE(CurrentSales - PriorSales, PriorSales) Create a DAX measure that computes each product's sales as a percentage of total sales across all products. The measure should ignore any product filters but respect other filters like region or date.
Solution
% of Total Sales =
VAR ProductSales = SUM(Sales[Revenue])
VAR TotalSales = CALCULATE(SUM(Sales[Revenue]), ALL(Products))
RETURN
DIVIDE(ProductSales, TotalSales) Create a DAX measure that computes a 3-month rolling average of sales. The measure should compute the average monthly sales over the last 3 months for each point in the visualization.
Solution
3M Avg Sales =
VAR EndDate = MAX(DateTable[Date])
RETURN
AVERAGEX(
DATESINPERIOD(DateTable[Date], EndDate, -3, MONTH),
CALCULATE(SUM(Sales[Revenue]))
) Quiz
1. What is the key difference between a calculated column and a measure in Power BI?
2. What does the CALCULATE function do?
3. Why is a date table required for time intelligence functions?
Flashcards
Question
What is the difference between filter context and row context in DAX?
Click to reveal answer
Answer
Filter context is the set of filters applied by slicers, visual filters, and the visual's rows and columns. Row context is the current row being evaluated in a calculated column or iterator. CALCULATE modifies filter context; iterators create row context.
Question
What does the ALL function do in DAX?
Click to reveal answer
Answer
ALL removes filters from a column, a table, or an entire table. It is used to compute totals that ignore slicer selections, such as denominators in percentage calculations.
Question
What is the purpose of an iterator function like SUMX?
Click to reveal answer
Answer
An iterator function evaluates an expression for each row in a table and then aggregates the results. It is used when the calculation involves multiple columns or requires row-level logic before aggregation.
Question
Why do you need variables in complex DAX measures?
Click to reveal answer
Answer
Variables improve readability and performance. They store the result of an expression so it can be referenced multiple times without recomputation. A measure with variables must end with a RETURN statement.
Revision Notes
Key Takeaways
- 1. Measures are computed at query time and respond to slicers; calculated columns are stored in the model
- 2. CALCULATE is the most important DAX function — it modifies filter context
- 3. ALL removes filters; ALLEXCEPT removes all filters except specified columns
- 4. Time intelligence functions require a continuous date table
- 5. Iterator functions evaluate an expression per row before aggregating
- 6. Variables improve readability and performance in complex measures
Interview Tips
- • Explain the difference between filter context and row context
- • Demonstrate how to compute a percentage of total using ALL
- • Show how to build a year-over-year growth measure
- • Describe when to use an iterator vs. a simple aggregation
Cheat Sheet
Cheat Sheet: Power BI DAX Measures
Measure vs. Calculated Column
- Calculated column: computed at data load, stored in model, used for grouping/filtering
- Measure: computed at query time, responds to slicers, used for aggregations
Essential Functions
CALCULATE(expr, filter1, filter2, ...)— modify filter contextFILTER(table, condition)— return filtered tableALL(table/column)— remove filtersALLEXCEPT(table, column)— remove all filters except specifiedDIVIDE(numerator, denominator)— safe division
Time Intelligence
TOTALYTD(expr, dates)— year-to-dateSAMEPERIODLASTYEAR(dates)— same period last yearDATEADD(dates, n, interval)— shift datesDATESINPERIOD(dates, start, n, interval)— date range
Iterators
SUMX(table, expr)— sum of expression per rowAVERAGEX(table, expr)— average of expression per rowCOUNTX(table, expr)— count of expression per row
Variables
Measure =
VAR Name = expression
RETURN
result