Mohamed A. Adel.

DAX & Advanced Calculations

Topic 6 of 12 · In Business Intelligence · September 9, 2026

⏱ 2 WeeksIntermediate–Advanced

Master advanced DAX: time intelligence, CALCULATE, FILTER, ALL, variables, complex measures and performance best practices.

++++FREE LEARNING PATHBUSINESS INTELLIGENCBUSINESS INTELLIGENCE · PHASE 2 · INTERMEDIATEDAX & ADVANCEDCALCULATIONSMaster advanced DAX: time intelligence, CALCULATE,…· 2 / 3INTERMEDIATE–ADVANCED2 WEEKSmotif / formulaΣ=96σ²1873 · 73 · MODULE 2.1

Overview

DAX (Data Analysis Expressions) is the formula language behind Power BI, SSAS Tabular and Power Pivot. In the previous topic you learned DAX basics — SUM, AVERAGE, CALCULATE with simple filters. This topic takes you to the next level: time intelligence (year-over-year, YTD, rolling averages), advanced context manipulation (FILTER, ALL, ALLEXCEPT, VALUES), variables for readability and performance, and the patterns that experienced BI developers use every day.

DAX is not just a function library — it is a context-based language. Understanding filter context and row context is the key that unlocks every advanced pattern. By the end of this topic, you will be able to write any DAX expression a BI project requires and optimize it for performance.

Before You Start

  • Have the Power BI model from the Power BI topic open, with fact_sales, dim_product, dim_customer and a dim_date marked as a date table
  • Install DAX Studio so you can verify the query plan of every measure you write
  • Install the free community edition of Tabular Editor if you want to organise measures into display folders
  • Keep the list of ten mini-project measures (MRR, MRR YoY Growth, MRR YTD, Rolling 3-Month Average, Cumulative Revenue, Percent of Total, Customer Segment, MoM Change, Cohort Month 0, Net Revenue Retention) ready
  • Get into the habit of starting every measure with VAR for intermediate results before you read a single section

Learning Objectives

  • Understand filter context and row context — the two pillars of DAX
  • Write time intelligence calculations: YTD, same-period-last-year, rolling averages
  • Master CALCULATE with complex filter expressions
  • Use ALL, ALLEXCEPT and REMOVEFILTERS to control filter removal
  • Write variables (VAR/RETURN) for readable, performant measures
  • Implement advanced patterns: ranking, percentile, cumulative, cohort
  • Identify and fix common DAX performance issues
  • Get started with Tabular Editor for advanced DAX development

Prerequisites

Continue from Power BI Deep Dive where you learned basic DAX (SUM, CALCULATE, DIVIDE) and data modeling. This topic builds directly on those foundations.

Filter Context and Row Context

Every DAX expression evaluates inside a context — the invisible set of filters that determines which rows a formula sees. There are two types:

Filter context is the set of active filters on the data model when a measure evaluates. When a visual shows "Revenue for Electronics in Q1," the filter context includes dim_product[category] = "Electronics" and dim_date[quarter] = "Q1". CALCULATE can modify this context.

Row context is the current row when a formula evaluates row-by-row (in a calculated column or inside an iterator like FILTER/SUMX). It is like a pointer moving through each row.

The critical insight: measures operate in filter context; calculated columns operate in row context; iterators (SUMX, FILTER, etc.) create their own row context inside a filter context.

CALCULATE: The Most Important Function

CALCULATE(expression, filter1, filter2, ...) evaluates an expression while modifying the filter context. Each filter argument can be:

  • A boolean expression — dim_product[category] = "Electronics" — adds a filter to the context
  • A table expression — FILTER(ALL(dim_date), dim_date[year] = 2025) — replaces the filter on that table
  • A direct column filter — dim_date[year] = 2025 — auto-wrapped in FILTER(ALL(...), ...)

Key behaviors:

  • CALCULATE removes existing filters on columns it filters, then applies the new filter
  • Filters in CALCULATE are applied after the expression evaluates its inner context
  • Nested CALCULATEs propagate context outward — each CALCULATE can modify what the next sees
CALCULATE Patterns.daxdax
// CALCULATE with multiple filter modifications

// Revenue for Electronics in 2025
Electronics Revenue 2025 =
CALCULATE(
  SUM(fact_sales[revenue]),
  dim_product[category] = "Electronics",
  dim_date[year] = 2025
)

// Revenue for all products (ignore product slicer) but keep region filter
All Products Revenue =
CALCULATE(
  SUM(fact_sales[revenue]),
  ALL(dim_product)
)

// Revenue for current category across ALL years
Category Revenue All Time =
CALCULATE(
  SUM(fact_sales[revenue]),
  ALL(dim_date)
)

ALL, ALLEXCEPT and REMOVEFILTERS

These functions remove filters from the context — they are the "undo" buttons of DAX:

FunctionWhat It DoesExample Use
ALL(table)Removes all filters from every column in the tablePercent of total: SUM(revenue) / CALCULATE(SUM(revenue), ALL(dim_product))
ALL(column)Removes filters from a specific column onlyIgnore product filter but keep date filter
ALLEXCEPT(table, col)Removes all filters except the specified column(s)Keep only the current product category, ignore everything else
REMOVEFILTERSSame as ALL but more readable (newer syntax)Use in CALCULATE to explicitly show filter removal intent
ALL and ALLEXCEPT.daxdax
// Percent of total revenue by category
Category Percent of Total =
DIVIDE(
  SUM(fact_sales[revenue]),
  CALCULATE(
    SUM(fact_sales[revenue]),
    ALL(dim_product)          -- remove product filters
  ),
  0
)

// Revenue share of total, ignoring all filters except date
Revenue Share =
DIVIDE(
  SUM(fact_sales[revenue]),
  CALCULATE(
    SUM(fact_sales[revenue]),
    ALLEXCEPT(dim_date, dim_date[year])  -- keep year filter only
  ),
  0
)

Variables: VAR and RETURN

Variables make DAX readable, debuggable and often faster. A variable stores a result once and reuses it — avoiding repeated calculation.

  • VAR defines a named intermediate result (a table or scalar value)
  • RETURN is required as the last statement — it outputs the final result
  • Variables are evaluated in the context where they are defined — they do NOT inherit the filter context of a subsequent CALCULATE
  • Use variables whenever a calculation has 2+ steps or a value is used more than once
Variables and Complex Measures.daxdax
// Complex measure with variables
Revenue Growth YoY =
VAR CurrentRevenue = SUM(fact_sales[revenue])
VAR PreviousRevenue =
  CALCULATE(
    SUM(fact_sales[revenue]),
    SAMEPERIODLASTYEAR(dim_date[date])
  )
VAR GrowthAmount = CurrentRevenue - PreviousRevenue
VAR GrowthPercent = DIVIDE(GrowthAmount, PreviousRevenue, 0)
RETURN
  GrowthPercent

// Variables for readability in ranking
Top 5 Products by Revenue =
VAR RankedProducts =
  ADDCOLUMNS(
    VALUES(dim_product[product_name]),
    "ProductRevenue", SUM(fact_sales[revenue])
  )
VAR Top5 =
  TOPN(5, RankedProducts, [ProductRevenue], DESC)
RETURN
  COUNTROWS(Top5)

Time Intelligence Functions

Time intelligence is the most common advanced DAX pattern. These functions rely on a properly marked date table (a contiguous date column marked as a date table in the model).

FunctionPurposeExample
DATEADD(date, n, period)Shift dates by n periodsRevenue 3 months ago
SAMEPERIODLASTYEAR(date)Same dates last yearYoY comparison
DATEYTD(date)Year-to-date rangeCumulative revenue this year
DATESBETWEEN(date, start, end)Custom date rangeRolling 90-day window
DATESINPERIOD(date, n, period)Last n periodsLast 6 months
TOTALYTD(expression, date)Year-to-date aggregationYTD revenue
Time Intelligence.daxdax
// Year-over-Year Growth
Revenue YoY Growth =
VAR CurrentRevenue = SUM(fact_sales[revenue])
VAR PriorYearRevenue =
  CALCULATE(
    SUM(fact_sales[revenue]),
    SAMEPERIODLASTYEAR(dim_date[date])
  )
RETURN
  DIVIDE(CurrentRevenue - PriorYearRevenue, PriorYearRevenue, 0)

// Year-to-Date Revenue
Revenue YTD =
TOTALYTD(SUM(fact_sales[revenue]), dim_date[date])

// Rolling 3-Month Average Revenue
Rolling 3M Avg Revenue =
AVERAGEX(
  DATESINPERIOD(dim_date[date], LASTDATE(dim_date[date]), -3, MONTH),
  [Total Revenue]
)

// Month-to-Date Revenue
Revenue MTD =
TOTALMTD(SUM(fact_sales[revenue]), dim_date[date])

Advanced Patterns

Common advanced DAX patterns used in production BI:

Cumulative / Running Total:

Advanced Patterns.daxdax
// Cumulative Revenue (running total over time)
Cumulative Revenue =
VAR LastVisibleDate = MAX(dim_date[date])
VAR CumulativeRevenue =
  CALCULATE(
    SUM(fact_sales[revenue]),
    FILTER(
      ALL(dim_date),
      dim_date[date] <= LastVisibleDate
    )
  )
RETURN
  CumulativeRevenue

// Dynamic Segmentation: High / Medium / Low Value
Customer Segment =
VAR CustomerRevenue = SUM(fact_sales[revenue])
RETURN
  SWITCH(
    TRUE(),
    CustomerRevenue >= 10000, "High Value",
    CustomerRevenue >= 3000, "Medium Value",
    "Low Value"
  )

// Cohort Analysis: Month 0 revenue per customer signup cohort
Cohort Month 0 Revenue =
VAR FirstPurchaseDate = MIN(fact_sales[date])
VAR CohortMonth =
  DATEDIFF(
    LOOKUPVALUE(dim_customer[signup_date], dim_customer[customer_key], EARLIER(fact_sales[customer_key])),
    FirstPurchaseDate,
    MONTH
  )
RETURN
  IF(CohortMonth = 0, SUM(fact_sales[revenue]), BLANK())

Performance Best Practices

  1. Use variables — store intermediate results to avoid recalculating the same expression
  2. Avoid FILTER on large tables — CALCULATE(SUM(revenue), dim_product[category] = "X") is faster than CALCULATE(SUM(revenue), FILTER(dim_product, dim_product[category] = "X"))
  3. Prefer columns over tables in filter arguments — filter specific columns, not entire tables
  4. Use SUMX/ADDCOLUMNS sparingly — iterators are row-by-row and slower than aggregate functions on large tables
  5. Minimize DISTINCTCOUNT — it is expensive; use approximate functions or pre-compute if possible
  6. Reduce model size — remove calculated columns, use appropriate data types, disable auto date/time if not needed
  7. Use DAX Studio to analyze query plans and identify bottlenecks

Hands-On Practice

Use Power BI Desktop with the AdventureWorksDW sample database or any sales dataset with a proper date dimension.

  1. Write a Revenue YoY Growth measure using SAMEPERIODLASTYEAR and variables.
  2. Write a YTD Revenue measure using TOTALYTD.
  3. Write a Rolling 3-Month Average Revenue measure using DATESINPERIOD.
  4. Write a Percent of Total Revenue by Category measure using ALL.
  5. Write a Cumulative Revenue measure using FILTER and ALL(dim_date).
  6. Write a Customer Segment measure using SWITCH to classify customers by lifetime revenue.
  7. Use DAX Studio to run each measure and check the query plan — identify any full table scans.
  8. Move to Tabular Editor and organize your measures into display folders.

Deeper Worked Examples

These examples push further than the core content — each one is a complete, step-by-step solution you can follow and then reproduce on your own.

YoY and rolling analysis for the finance committee

Problem. A SaaS company reports MRR by plan. The committee wants one matrix showing current month MRR, the same month last year, year-over-year growth percentage and a rolling three-month average, all robust to calendar months with no revenue at all.
1

Step 1

Confirm dim_date is marked as a date table in the model so every time intelligence function operates on a contiguous date column with no gaps.
2

Step 2

Write the base measure as Current MRR = SUM(fact_revenue[mrr]).
3

Step 3

Add Prior Year MRR = CALCULATE([Current MRR], SAMEPERIODLASTYEAR(dim_date[date])) and the growth measure as YoY Growth = DIVIDE([Current MRR] - [Prior Year MRR], [Prior Year MRR], 0) so empty prior years return blank or zero rather than an error.
4

Step 4

Add Rolling 3M Avg MRR = AVERAGEX(DATESINPERIOD(dim_date[date], LASTDATE(dim_date[date]), -3, MONTH), [Current MRR]) for the trailing window.
5

Step 5

Place the four measures in a matrix with dim_plan tier on the rows and the date timeline on the columns, verifying that months with no revenue return blank values cleanly.
6

Step 6

Open DAX Studio, run each measure, confirm the queries reuse the date index rather than scanning the full table, and file the measures under a Time Intelligence display folder in Tabular Editor.

Percent of total and dynamic segmentation without breaking slicers

Problem. A single page needs market share by category and a High, Medium or Low customer segment. The first attempt uses CALCULATE with ALL(dim_product) and returns the correct share, but the customer segment ignores the region slicer and a bare SUMX iterator re-scans the whole table.
1

Step 1

Write Category Share = DIVIDE(SUM(fact_sales[revenue]), CALCULATE(SUM(fact_sales[revenue]), ALL(dim_product)), 0) so the denominator ignores product filters but continues to honour the date and region slicers.
2

Step 2

Note that ALL(dim_product) deliberately removes slicer and visual filters on product columns; where the intent is narrower, prefer ALLEXCEPT or REMOVEFILTERS to keep other filters visible.
3

Step 3

Rewrite the segment as a measure with variables: assign Customer Revenue from SUM(fact_sales[revenue]), then RETURN SWITCH(TRUE(), Customer Revenue at least 10,000 then 'High Value', at least 3,000 then 'Medium Value', otherwise 'Low Value'), keeping the thresholds explicit.
4

Step 4

Remove the SUMX row-wise iteration entirely, because the segment is evaluated per visible customer by the filter context of the visual, so no iterator is required.
5

Step 5

Test in DAX Studio and the report's Performance Analyzer, confirming the share denominator responds to a date slicer move but not to a product filter change.
6

Step 6

Confirm the segment buckets recalculate per row without re-scanning fact_sales, then document each measure in Tabular Editor with a display folder.

Common Mistakes

  • Using FILTER(ALL(table), ...) when CALCULATE with a direct filter expression would be faster and simpler
  • Forgetting that variables are evaluated in their defining context — they do not change when CALCULATE modifies context later
  • Not marking a date table — time intelligence functions produce wrong results without a contiguous date column
  • Using calculated columns for time intelligence — always use measures with time intelligence functions
  • Using ALL without understanding it removes slicer filters — unexpected results on reports
  • Iterating over entire tables with SUMX when a simple SUM would work — iterators are slower
  • Not testing DAX with DAX Studio — you cannot optimize what you cannot measure

Real-World Usage

DAX mastery separates a Power BI user from a Power BI developer. In production, you will write time intelligence for fiscal year reports, dynamic segmentation for customer analytics, cumulative charts for goal tracking and complex KPIs that combine multiple business rules. The patterns in this topic — CALCULATE, ALL, variables, time intelligence — are the vocabulary of every advanced Power BI model. SQLBI's DAX patterns repository is the reference you will return to throughout your career.

Mini Project

Business problem: A subscription SaaS company needs advanced analytics: year-over-year revenue growth, rolling averages, customer segmentation, cohort analysis and cumulative revenue tracking.

Dataset: use a subscription dataset with fact_revenue (date, customer_key, mrr), dim_customer (signup_date, plan, segment) and dim_date (full date dimension marked as date table).

Objective: write 10 advanced DAX measures that demonstrate mastery of the patterns from this topic.

1

Task 1

Set up the model: ensure dim_date is marked as a date table with a contiguous date column.
2

Task 2

Measure 1: MRR (Monthly Recurring Revenue) — simple SUM.
3

Task 3

Measure 2: MRR YoY Growth — using SAMEPERIODLASTYEAR and variables.
4

Task 4

Measure 3: MRR YTD — using TOTALYTD.
5

Task 5

Measure 4: Rolling 3-Month Average MRR — using DATESINPERIOD and AVERAGEX.
6

Task 6

Measure 5: Cumulative Revenue since company founding — using FILTER and ALL(dim_date).
7

Task 7

Measure 6: Percent of Total MRR by Plan — using ALL(dim_customer) or ALL(dim_plan).
8

Task 8

Measure 7: Customer Segment (High/Medium/Low) — using SWITCH and variables.
9

Task 9

Measure 8: Month-over-Month MRR Change — using DATEADD.
10

Task 10

Measure 9: Cohort Month 0 Revenue — first month revenue per signup cohort.
11

Task 11

Measure 10: Net Revenue Retention — current MRR / MRR 12 months ago.
12

Task 12

Test all measures in Power BI, check performance in DAX Studio, organize in Tabular Editor.
DeliverableWhat It Proves
10 advanced DAX measuresYou can implement complex business logic
Time intelligence in 4+ measuresYou master date-based calculations
ALL/CALCULATE pattern in 3+ measuresYou understand filter context manipulation
Variables used in 5+ measuresYou write readable, performant DAX
Performance checked in DAX StudioYou can optimize DAX queries
Organized in Tabular EditorYou use professional development tools

Key Takeaways

  • Filter context (visual/slicer filters) and row context (row-by-row iteration) are the two pillars of DAX
  • CALCULATE modifies filter context — it is the most important function in DAX
  • ALL, ALLEXCEPT, REMOVEFILTERS remove filters for percent-of-total, ranking and cross-group calculations
  • VAR/RETURN improves readability and performance by storing intermediate results
  • Time intelligence requires a marked date table with contiguous dates
  • SAMEPERIODLASTYEAR, TOTALYTD, DATESINPERIOD, DATEADD are the core time functions
  • Performance: use direct filters over FILTER(ALL(table)), minimize iterators, reduce model size
  • Tabular Editor and DAX Studio are essential professional tools

Key Terms & Glossary

The vocabulary this topic uses, defined the way a working professional would.
TermMeaning
Filter contextThe set of active filters, from slicers, visual filters or CALCULATE, that decides which rows a measure evaluates over.
Row contextThe current-row pointer during row-by-row evaluation inside a calculated column or an iterator such as SUMX or FILTER.
CALCULATEThe DAX function that removes and reapplies filter context; the single most important function because it controls which rows every other calculation sees.
IteratorA function such as SUMX, AVERAGEX or FILTER that walks a table row by row in its own row context, generally slower than direct aggregation.
Time intelligenceDAX functions such as DATEADD, SAMEPERIODLASTYEAR, TOTALYTD and DATESINPERIOD that shift or window dates, and that require a marked contiguous date table.
VAR / RETURNThe syntax that names intermediate results inside a measure, improving readability and letting the engine compute a value once and reuse it.

Learning Resources

These curated sources go deeper than this module: official docs, books, interactive practice, and full courses. Mix them into your routine to go from comfortable to professional.

DAX Patterns — sqlbi.com

The definitive collection of DAX patterns for every common BI calculation, by Marco Russo and Alberto Ferrari.

Open resource ↗

DAX Guide — dax.guide

Complete reference for every DAX function with syntax, examples and remarks.

Open resource ↗

Coursera — Advanced DAX for Power BI

A focused course on advanced DAX patterns and performance optimization.

Open resource ↗

Tabular Editor (GitHub)

Free, open-source external editor for Power BI / SSAS Tabular models with C# scripting.

Open resource ↗

DAX Studio

Free tool for querying, analyzing and optimizing DAX queries against Power BI / SSAS models.

Open resource ↗

SQLBI YouTube Channel

Marco Russo and Alberto Ferrari's channel — deep dives into DAX, data modeling and Power BI optimization.

Open resource ↗

Chapter Quiz

Put this lesson to work: answer the questions below, get instant feedback and a pass/fail score. Score 70% or higher before you move on — that is your gate to the next topic.

Chapter Quiz — DAX & Advanced Calculations

Filter context, CALCULATE and ALL, VAR/RETURN, time intelligence and why direct filters beat FILTER iterators.

0 / 5

Question 1 of 5

Which measure shows revenue across ALL products even when a product slicer is active?

Knowledge Check

Filter context is the set of active filters determining which rows a measure sees (from slicers, visual filters, or CALCULATE). Row context is the current row pointer in calculated columns or iterators (SUMX, FILTER). Measures operate in filter context; calculated columns operate in row context.
ALL(dim_product) removes all filters from the dim_product table, including any slicer or visual-level filters on product columns. CALCULATE then evaluates SUM(revenue) with no product filters active, returning the grand total.
Whenever a value is used more than once, or when a calculation has multiple steps. Variables store the result once (avoiding recalculation), improve readability (naming intermediate results), and prevent common context-related bugs.
Time intelligence functions (DATEADD, SAMEPERIODLASTYEAR, TOTALYTD) expect a contiguous date column with no gaps. A date column from a fact table may have missing dates (no sales on some days), causing incorrect results. A dedicated dim_date table guarantees continuity.
ALL(dim_product) removes all product filters, but fact_sales still needs a relationship to dim_date for the filter context. Without proper context, SUMX iterates over all products and sums revenue without the intended product filter — likely returning a wrong number. Use CALCULATE with specific filters instead.

End-of-Topic Exam

Hint: Measures see filters, columns walk row by row; this is the very first core section.

Model answer: Filter context is the set of active filters that decides which rows a measure evaluates over, arriving from slicers, visual filters or CALCULATE. Row context is the current-row pointer during row-by-row evaluation in a calculated column or iterator. In the example, CALCULATE first removes existing filters on dim_date[year], applies the direct filter dim_date[year] = 2025 (implicitly wrapped like FILTER(ALL(dim_date), ...)) and then evaluates SUM(revenue) against only 2025 rows; the row context of any inner iterator such as SUMX is defined by that iterator itself and is not changed by CALCULATE. Full marks require stating both the filter removal and the filter application.

Hint: The 'Always use a dedicated date table' callout explains the failure mode.

Model answer: Time intelligence functions require a contiguous date column with no gaps, and the fact table's date column has gaps because no sales happen on some days, so SAMEPERIODLASTYEAR and TOTALYTD produce missing or shifted periods. The correct setup is a dedicated dim_date that contains every date in the range with no gaps, marked as a Date Table in the model, with facts related to it. Before the fix you would observe YTD totals ending mid-period, blank months and year-over-year comparisons off by one day because the non-contiguous column misaligns the window.

Hint: The performance best-practices list and the ALL warning callout discuss this contrast.

Model answer: The direct column filter is a simple predicate passed to the storage engine, evaluated against the column index without materialising a filtered table. The FILTER form iterates through every row of dim_product in a row context to build the filtered table before it is applied, which is slower and cannot be pushed down to the index in the same way. The performance gap grows with the size of the dimension, so the guidance is to prefer direct filters and reserve FILTER for genuinely complex row-level logic.

Next Step

Congratulations — you just finished DAX & Advanced Calculations. The next topic is Dashboard Design & Best Practices. Press on when you are ready: it will build directly on what you practiced here.