~12 min read

DAX Patterns

Time, Variables, and Beyond

Powerful patterns that turn DAX knowledge into real-world solutions.

Part 3 of 3: DAX Fundamentals
Scroll to explore

Building On Context

Everything you've learned so far leads here

You've built the foundation. Now it's time to put it to work.

Time Intelligence

Period comparisons: YTD, Prior Cycle, QTD

Variables

Cleaner, faster, more debuggable formulas

Relationships

Active, inactive, and virtual via USERELATIONSHIP

Calculation Groups

Eliminate duplication across your entire model

Time Intelligence

Comparing data across time periods: the most common reporting need

Prerequisite: A proper date table. Time intelligence functions need a table with contiguous dates, every single day, no gaps. Mark it as a date table in your model.

These functions return tables of dates. You pass them into CALCULATE, and CALCULATE uses those dates to override the current filter context.

Revenue YTD = CALCULATE( [Total Revenue], DATESYTD('Stardate'[Date]) )
Revenue Prior Cycle = CALCULATE( [Total Revenue], DATEADD('Stardate'[Date], -1, YEAR) )

Function Reference

DATESYTD( 'Stardate'[Date], "MM/DD" )

Returns all dates from January 1 (or your fiscal year start) through the last date in the current filter context. Resets on Jan 1 each year. Optional second parameter sets a custom fiscal year end date.

Example: On Mar 15, returns Jan 1 - Mar 15 (75 days).

DATESQTD( 'Stardate'[Date] )

Returns dates from the current quarter start through the last date in context. Resets at each quarter boundary (Q1=Jan, Q2=Apr, Q3=Jul, Q4=Oct).

Example: On Mar 15 (Q1), returns Jan 1 - Mar 15.

DATESMTD( 'Stardate'[Date] )

Returns dates from the 1st of the current month through the last date in context. Resets on the first of each month.

Example: On Mar 15, returns Mar 1 - Mar 15 (15 days).

DATEADD( 'Stardate'[Date], <intervals>, <YEAR|QUARTER|MONTH|DAY> )

Shifts the entire date filter by the specified interval. Use -1 YEAR for prior year, -1 QUARTER for prior quarter, etc. The most flexible time intelligence function.

Example: DATEADD(..., -1, YEAR) on Mar 15, 2226 returns Mar 15, 2225.

Interactive Stardate

Click any date to set it as "today" and see what each function returns, including calculated dollar amounts.

YTD
QTD
MTD

Logic Patterns

SWITCH, SELECTEDVALUE, and making reports dynamic

SWITCH: The Better IF Chain

Nested IFs (Messy)

IF([Revenue] > 1000000, "High",
  IF([Revenue] > 500000, "Medium",
    "Low"
  )
)
vs

SWITCH + VAR (Clean)

VAR Rev = [Revenue]
RETURN
  SWITCH( TRUE(),
    Rev > 1000000, "High",
    Rev > 500000, "Medium",
    "Low"
  )

Order matters: SWITCH returns the first match. Put restrictive conditions first. Storing [Revenue] in a VAR means it evaluates once, not twice.

Note: Field Parameters

Field parameters let users dynamically change which measure or column appears in a visual without any SWITCH logic. Created from the Modeling ribbon, they use NAMEOF() references under the hood. Power BI's slicer integration handles the rest.

SELECTEDVALUE: Slicer-Driven Logic

Returns a value when a column is filtered to exactly one value. Multi-select or no selection returns the fallback.

Slicer: Cycle

Revenue for All Cycles
= "Revenue for " & SELECTEDVALUE( 'Stardate'[Cycle], "All Cycles" )

Output: "Revenue for All Cycles"

Common Uses

Dynamic Titles

Report titles that change based on slicer selections. Great for exported PDFs.

What-If Parameters

SELECTEDVALUE reads the slider position and feeds it into calculations.

Measure Switching

One visual, multiple metrics via a disconnected slicer table.

Variables in DAX

Write once, use many: cleaner code and better performance

Variables store intermediate results, give them names, and prevent recalculation. You just saw SWITCH reference [Revenue] twice in Logic Patterns. With a variable, that value is calculated once and reused every time.

Revenue Band =
VAR CurrentRevenue = [Total Revenue]
VAR Result =
    SWITCH( TRUE(),
        CurrentRevenue > 5000000, "High",
        CurrentRevenue > 1000000, "Mid",
        CurrentRevenue > 0, "Low",
        "No Sales"
    )
RETURN Result

Variables capture context at definition time. CurrentRevenue is evaluated once and referenced in three SWITCH conditions. Without the variable, [Total Revenue] would recalculate for each condition check.

Why Use Variables?

Performance Evaluated once, reused everywhere.
Readability Named values replace repeated expressions.
Debugging Change RETURN to any VAR name to inspect values.

Step Through the Evaluation

VAR CurrentRevenue = [Total Revenue]
VAR Result = SWITCH( TRUE(), ... )
  CurrentRevenue > 5000000 // FALSE
  CurrentRevenue > 1000000 // TRUE -> "Mid"
RETURN Result
Revenue Band
--
Click "Step" to walk through how DAX evaluates this measure. Each variable is computed once and reused.
Step 0 of 5

Relationships & USERELATIONSHIP

Multiple date columns, one active relationship, and how to switch

Your Trades table has a TradeDate and a DeliveryDate. Both connect to Stardate. But Power BI only allows one active relationship between two tables. The fix: create inactive relationships and activate them on demand with USERELATIONSHIP.

Active vs Inactive Paths

Toggle a path to switch the active relationship and see how the numbers shift.

Stardate
Date
Year
Quarter
1
* Active: TradeDate
1
* Inactive: DeliveryDate
Trades
TradeDate
DeliveryDate
Credits
$1.5M $1.0M $0.5M 0
$1.34M
$1.18M
$1.29M
$1.37M
$1.41M
$1.30M
$1.05M
$1.23M
Q1 Q2 Q3 Q4
Trade Date Delivery Date
Total Revenue = SUM( 'Trades'[Credits] )
// Uses default active relationship (TradeDate)
i
Instead of USERELATIONSHIP, many teams use a Calculation Group to switch date paths. See the Calculation Groups section below.

Calculation Groups

Write time intelligence once, apply it to every measure

Think of a calculation group as a modifier that wraps around any measure. You write your base measures once (Revenue, Cost, Profit), then the calculation group applies transformations like YTD, Prior Cycle, or YoY% to whichever measure is in the visual. Instead of 3 measures x 3 time views = 9 measures, you maintain 3 base measures + 1 calc group.

The Duplication Problem

Without Calc Groups

Revenue YTD Cost YTD Profit YTD Revenue PY Cost PY Profit PY Revenue YoY% Cost YoY% Profit YoY%
9 measures (and growing)
vs

With Calc Groups

Revenue Cost Profit
×
Actual YTD Prior Cycle YoY %
3 base × 4 items = all combos

SELECTEDMEASURE()

The function that makes it work. It returns whichever measure the user placed in the visual:

// This ONE item applies YTD to ANY measure
YTD = CALCULATE( SELECTEDMEASURE(), DATESYTD('Stardate'[Date]) )

See It in Action

Select a calculation item to see how it transforms the base measure. Notice the format change on YoY%.

Base Measure
[Total Revenue]
×
Calculation Item
// Actual: No modification
SELECTEDMEASURE()
Currency
$5,082,000
The Actual item returns the measure unchanged. SELECTEDMEASURE() evaluates [Total Revenue] directly.

Under the Hood

How the DAX engine processes your queries in two stages

Every time a visual renders, DAX runs a two-stage process. Understanding this changes how you think about performance.

Storage Engine

Retrieves data from the in-memory VertiPaq model. Columnar, compressed, parallelized across cores.

Formula Engine

Evaluates DAX expressions: context logic, CALCULATE, iterators, variables. Single-threaded.

Result

Final value for your visual. More work in Stage 1 = faster overall.

Fast vs Slow Patterns

Storage Engine Friendly Fast

  • Simple aggregations (SUM, COUNT, MIN, MAX)
  • Star schema with clean foreign keys
  • Direct column references
  • CALCULATE with table filters

Formula Engine Heavy Slower

  • Complex iterators (SUMX with nested logic)
  • Nested CALCULATE inside CALCULATE
  • Row-by-row processing patterns
  • Many-to-many without bridge tables

The performance rule of thumb: Push as much work to the Storage Engine as possible. Star schema + simple aggregations = the Storage Engine does the heavy lifting = fast. When a measure feels slow, ask: "Can I restructure this to let the Storage Engine do more?"

Key Takeaways

Three parts, one complete foundation

Time Intelligence Returns Tables

DATEADD, SAMEPERIODLASTYEAR, and friends return date tables that CALCULATE uses as filters, not scalar values.

Variables Capture Context at Definition

Write once, reuse many times. The value is frozen when VAR is declared, not when it is referenced.

USERELATIONSHIP Activates Inactive Paths

One active relationship per pair of tables; switch to alternates inside CALCULATE on demand.

Calculation Groups Eliminate Duplication

One calc group with items (YTD, PY, YoY%) replaces dozens of nearly identical measures.

Push Work to the Storage Engine

Simple aggregations on star schema are parallelized and fast. Complex DAX runs single-threaded in the Formula Engine.

These Patterns Build on Context

Every technique here (time intel, variables, relationships, calc groups) relies on the filter context and CALCULATE foundations from Parts 1 and 2.

Tools for the Next Step

DAX Studio

Free external tool for writing/testing DAX queries with performance tracing and storage engine diagnostics.

Free Tool

DAX Query View

Built into Power BI Desktop. Write DAX queries and see results without any external installation.

Built-in

Performance Analyzer

Shows exactly how long each visual takes, split between storage engine and formula engine time.

Built-in

You've worked through all three parts. The concepts you've internalized (context, CALCULATE, variables, time intelligence) are the same building blocks professionals use every day. Everything from here is applying these patterns to new problems. You've got the foundation. The rest is practice.

1 / 9