luvv to helpDiscover the Best Free Online Tools
Topic 5 of 8

KPI Variance And Percent Change

Learn KPI Variance And Percent Change for free with explanations, exercises, and a quick test (for BI Developer).

Published: December 24, 2025 | Updated: December 24, 2025

Why this matters

As a BI Developer, you will constantly explain performance: Are we up or down? By how much? Compared to what? Variance and percent change are the backbone of KPI reporting in executive dashboards, product analytics, and finance reports.

  • Compare Actual vs Target for revenue, costs, or operational KPIs.
  • Show Month-over-Month (MoM) and Year-over-Year (YoY) trends.
  • Flag anomalies with conditional formatting based on percent changes.
  • Enable drilldowns that keep logic consistent from product to total.

Tip: Your quick test is available to everyone; sign in to save your progress automatically.

Concept explained simply

Variance answers “How much did we move?” Percent change answers “How fast did we move?”

  • Variance = Current - Comparison
  • Percent change = (Current - Comparison) / Comparison

In DAX, the key is to compute the comparison value by changing filter context (time, scenario, etc.) and then calculate the difference safely (avoid divide-by-zero, handle blanks).

Mental model

  • Start with a base measure (e.g., [Sales]).
  • Use CALCULATE to get the comparison (previous period, target, last year).
  • Use DIVIDE(numerator, denominator, alternateResult) to avoid errors when denominator is zero or blank.
  • Return BLANK() when comparison is missing; it keeps visuals clean.
  • Use variables (VAR) for readability and performance.

Core DAX patterns

Variance to Target
-- Base measures
a) Sales := SUM ( Sales[SalesAmount] )
b) Target := SUM ( Targets[TargetAmount] )

-- Variance to Target
Variance to Target := [Sales] - [Target]

-- Variance % to Target
Variance % to Target := 
VAR Curr = [Sales]
VAR Tgt  = [Target]
RETURN DIVIDE( Curr - Tgt, Tgt )

Format: Variance as Whole/Decimal; Variance % as Percentage with 1–2 decimals.

Month-over-Month (MoM) % change
-- Requires a proper Date table marked as Date table
Previous Month Sales := 
CALCULATE( [Sales], DATEADD( 'Date'[Date], -1, MONTH ) )

MoM % Sales := 
VAR Curr = [Sales]
VAR Prev = [Previous Month Sales]
RETURN DIVIDE( Curr - Prev, Prev )

Works at any granularity as long as a single month is in context (or totals aggregate across months).

Year-over-Year (YoY) % change
Previous Year Sales := 
CALCULATE( [Sales], SAMEPERIODLASTYEAR( 'Date'[Date] ) )

YoY % Sales := 
VAR Curr = [Sales]
VAR Prev = [Previous Year Sales]
RETURN DIVIDE( Curr - Prev, Prev )

For fiscal calendars, replace with parallelperiod on your fiscal month column.

Worked examples

1) Actual vs Target (card + matrix)

Goal: Show Variance and Variance % for current selection.

Variance to Target := [Sales] - [Target]
Variance % to Target := 
VAR Curr = [Sales]
VAR Tgt  = [Target]
RETURN DIVIDE( Curr - Tgt, Tgt )
  • Card visuals for Variance and Variance %.
  • Matrix by Product shows contribution; total row shows overall variance.
  • If a product has no target, the % returns blank to avoid noise.

2) MoM % on a line chart

Previous Month Sales := CALCULATE( [Sales], DATEADD( 'Date'[Date], -1, MONTH ) )
MoM % Sales := 
VAR Curr = [Sales]
VAR Prev = [Previous Month Sales]
RETURN DIVIDE( Curr - Prev, Prev )
  • Use Month on axis, MoM % as values.
  • First month shows blank (no previous month).
  • Totals represent the weighted change across months in the filter context.

3) YoY % with slicers

Previous Year Sales := CALCULATE( [Sales], SAMEPERIODLASTYEAR( 'Date'[Date] ) )
YoY % Sales := 
VAR Curr = [Sales]
VAR Prev = [Previous Year Sales]
RETURN DIVIDE( Curr - Prev, Prev )
  • Use a Year slicer; measure respects filters like Region and Product.
  • For partial-year selections, the comparison aligns to the same dates last year.

4) Safe totals with ISINSCOPE (optional)

Variance % Safe := 
VAR Curr = [Sales]
VAR Comp = [Previous Year Sales]
RETURN 
IF(
    ISINSCOPE( 'Product'[Product] ),
    DIVIDE( Curr - Comp, Comp ),
    -- At higher levels, recompute explicitly for clarity
    DIVIDE( Curr - Comp, Comp )
)

Often the plain formula is fine; this pattern is useful when totals look unexpected due to different weighting at aggregate levels.

Who this is for

  • BI Developers building KPI dashboards and variance analysis.
  • Data analysts transitioning to DAX-based modeling.
  • Analytics engineers who need consistent time comparisons.

Prerequisites

  • A star schema with a proper Date table marked as the date table.
  • Basic DAX measures (SUM, CALCULATE) and filter context understanding.
  • Familiarity with your business’s KPI definitions (Actual, Target, Budget).

Learning path

  1. Set up base measures ([Sales], [Target]).
  2. Build comparison measures (previous month/year; targets).
  3. Add variance and percent change with DIVIDE.
  4. Test at different granularities (day, month, product, total).
  5. Format, color-code, and document assumptions.

Edge cases and formatting

  • Divide-by-zero: Always use DIVIDE to avoid errors and return BLANK().
  • Missing comparison period: Expect BLANK() for first month or new products.
  • Targets not aligned to date grain: Aggregate targets to match the visual’s context.
  • Sign conventions: Decide whether a negative variance is good (e.g., lower cost). Document it.
  • Percent formats: Use percentage with 1–2 decimals; avoid more unless needed.

Exercises you will build now

Dataset assumption (typical model):

  • Fact table: Sales(Date, Product, Segment, Quantity, SalesAmount)
  • Targets table: Targets(Date, Product, TargetAmount)
  • Date table: 'Date' with continuous dates; marked as Date table

Replicate the following exercises. When done, compare with the solutions in each exercise card below.

  • Create and validate base measures: [Sales], [Target].
  • Build Variance and Variance % to target.
  • Compute MoM % and YoY %.
  • Check results at Product and Total levels.
Exercise 1 — Actual vs Target variance

Create:

  • [Sales] = SUM(Sales[SalesAmount])
  • [Target] = SUM(Targets[TargetAmount])
  • [Variance to Target] and [Variance % to Target]

Validate on visuals: Card for Variance; Card for Variance %; Matrix by Product.

  • Expected behavior: Numbers add up at total; Variance % is blank where Target is blank or zero; formatting is clear.
Exercise 2 — MoM and YoY % change

Create:

  • [Previous Month Sales] with DATEADD -1 month
  • [MoM % Sales] using DIVIDE
  • [Previous Year Sales] with SAMEPERIODLASTYEAR
  • [YoY % Sales] using DIVIDE

Validate on visuals: Line chart by Month; Matrix by Year and Month.

  • Expected behavior: First month shows blank MoM; first year shows blank YoY; respects filters (Region, Segment).

Common mistakes and self-check

  • Forgetting a proper Date table: Time-intelligence (DATEADD/SAMEPERIODLASTYEAR) won’t work reliably. Self-check: Is your Date table marked as the Date table?
  • Using direct division: Risks divide-by-zero errors or misleading infinities. Self-check: Did you use DIVIDE?
  • Comparing across mismatched grains: Daily sales vs monthly targets. Self-check: Do the measures aggregate the same grain in the current filter context?
  • Hard-coding filters in measures: Breaks slicers. Self-check: Do visuals still respond to slicers after you add CALCULATE?
  • Ambiguous totals: Totals not matching expectations. Self-check: Recreate total logic with explicit vars to understand weighting.

Practical projects

  • Executive KPI board: Cards for Actual, Target, Variance, Variance %, with MoM and YoY on sparklines.
  • Category drilldown: Category → Subcategory → Product variance analysis with conditional formatting.
  • Alerting-ready measure pack: Measures that return BLANK() for noisy edge cases and consistent signs for alerts.

Next steps

  • Add rolling windows (Last 3 Months, Last 12 Months) for smoother comparisons.
  • Support Budget vs Forecast vs Actual with a Scenario dimension.
  • Document business rules (good/bad direction, rounding) in your model notes.

Mini challenge

Create a Rolling 3-Month % Change vs the previous 3 months:

  1. Make [Sales L3M] = sales for the last 3 complete months.
  2. Make [Sales Prev L3M] = the 3 months before that.
  3. Return DIVIDE([Sales L3M] - [Sales Prev L3M], [Sales Prev L3M]).
Hint

Use DATESINPERIOD for L3M and shift it by -3 months for the previous block.

Suggested solution
Sales L3M := 
CALCULATE( [Sales], DATESINPERIOD( 'Date'[Date], MAX('Date'[Date]), -3, MONTH ) )

Sales Prev L3M := 
VAR EndDate = MAX( 'Date'[Date] )
VAR PrevEnd  = EOMONTH( EndDate, -3 )
RETURN 
CALCULATE( [Sales], DATESINPERIOD( 'Date'[Date], PrevEnd, -3, MONTH ) )

L3M % Change := 
VAR Curr = [Sales L3M]
VAR Prev = [Sales Prev L3M]
RETURN DIVIDE( Curr - Prev, Prev )

Ready? Quick Test

Take the quick test below. Everyone can take it for free; sign in to save your progress.

Practice Exercises

2 exercises to complete

Instructions

Build two measures that compare Actual Sales to Target.

  1. Create [Sales] = SUM(Sales[SalesAmount]).
  2. Create [Target] = SUM(Targets[TargetAmount]).
  3. Create [Variance to Target] = [Sales] - [Target].
  4. Create [Variance % to Target] = DIVIDE([Sales] - [Target], [Target]).
  5. Place Variance and Variance % on cards; add a matrix by Product.
  • Formatting: Variance as decimal or whole; Variance % as Percentage (1–2 decimals).
  • Behavior: BLANK() when Target is blank or zero.
Expected Output
Cards show overall Variance and Variance %. Matrix shows per-Product differences; totals aggregate. Variance % is blank for products with no targets.

KPI Variance And Percent Change — Quick Test

Test your knowledge with 8 questions. Pass with 70% or higher.

8 questions70% to pass

Have questions about KPI Variance And Percent Change?

AI Assistant

Ask questions about this tool