Microsoft Power BI • DAX and Measures

Basic DAX Measures — Write basic measures using SUM, AVERAGE, COUNT, DISTINCTCOUNT

Master the foundational DAX aggregation functions that power dynamic, context-aware calculations in Power BI data models.

Microsoft Power BI emblem with a dashboard, stacked chart, and database.
Inside this lesson 10 sections 5 questions 9 figures

SECTION 1

Historical Context & Motivation

The story of Data Analysis Expressions (DAX) is deeply intertwined with Microsoft's evolution from desktop spreadsheet tooling toward enterprise-grade, in-memory analytics. Before DAX existed, analysts relied on Excel formulas, VBA macros, or SQL queries to compute aggregations, each approach carrying significant limitations when faced with the kind of interactive, slice-and-dice reporting modern business intelligence demands. DAX was designed from the ground up to operate over columnar, in-memory data models, enabling calculations that respond dynamically to user-driven filter contexts—something that static spreadsheet formulas could never achieve cleanly.

2009 • PowerPivot for Excel

Microsoft released PowerPivot as an Excel add-in, introducing the xVelocity (VertiPaq) in-memory engine. DAX debuted as the formula language for creating calculated columns and measures within these models, giving analysts SQL-like aggregation power inside Excel.

2010–2013 • SQL Server Analysis Services (Tabular)

Microsoft integrated the same DAX engine into SSAS Tabular models, positioning DAX as a first-class analytical language for enterprise BI. Functions like SUM, AVERAGE, COUNT, and DISTINCTCOUNT became standard building blocks for corporate dashboards.

2015 • Power BI Desktop Launch

Power BI Desktop was released as a standalone, free-to-download application. DAX measures became the primary mechanism for defining business logic—revenue calculations, KPIs, and statistical summaries—that responded to slicers and visual-level filters.

2018–Present • DAX Maturity and Ecosystem Growth

The DAX language expanded with hundreds of functions, performance optimizations, and integration into Azure Analysis Services and Power BI Premium. Community resources like SQLBI and DAX.do accelerated adoption, making DAX fluency a core competency for data professionals.

The central question DAX measures address is this: how do you define a calculation once, and have it automatically re-evaluate correctly across every possible combination of filters, slicers, row contexts, and visual groupings that a user might create in a report? The answer lies in the concept of measures—named formulas that exist in the model metadata rather than in any physical column, and that are evaluated lazily at query time within a dynamically determined filter context. Understanding SUM, AVERAGE, COUNT, and DISTINCTCOUNT as your first measures is the essential gateway to mastering the entire DAX ecosystem.

SECTION 2

Core Principles & Definitions

Before writing any DAX formula, it is essential to internalize the conceptual framework that distinguishes DAX from procedural programming languages or even from standard SQL. DAX measures operate within a declarative evaluation model: you specify what to compute, not how to iterate. The engine determines the optimal execution plan using the VertiPaq storage engine and the formula engine, operating over compressed, columnar data structures. The following core principles govern how basic measures behave.

1

Measures vs. Calculated Columns

A measure is evaluated at query time within the current filter context and never materializes as a stored column. A calculated column is computed row-by-row during data refresh and stored in memory. Measures are preferred for aggregations because they adapt to slicers dynamically.
2

Filter Context

Every measure evaluation occurs within a filter context—the set of active filters propagated from slicers, visual axes, page filters, and report filters. SUM('Sales'[Amount]) returns different values depending on which product, region, or date range is currently filtered.
3

Implicit vs. Explicit Measures

Dragging a numeric column into a visual creates an implicit measure (default aggregation). Writing a DAX formula creates an explicit measure that you fully control. Explicit measures are reusable, testable, and support complex logic.
4

Aggregation Functions

DAX provides a family of aggregation functions—SUM, AVERAGE, COUNT, COUNTA, COUNTBLANK, COUNTROWS, DISTINCTCOUNT, MIN, MAX—that iterate over the rows visible in the current filter context and return a scalar value. These are the atomic building blocks of all DAX measures.
5

Measure Syntax

A measure definition follows the pattern: MeasureName = FUNCTION('Table'[Column]). The measure name is assigned with an equals sign, and column references use the fully qualified 'TableName'[ColumnName] notation.
✦

KEY TAKEAWAY

Think of a DAX measure as a parameterized function whose parameters are not passed explicitly but are instead determined by the filter context at evaluation time—much like a method in an event-driven architecture that reads its state from the ambient environment. When you write Total Sales = SUM('Sales'[Amount]), you are not hard-coding which rows to sum; you are declaring an intent, and the evaluation engine resolves it against whatever filters are active at the moment the visual requests the value.

SECTION 3

Visual Explanation — How Filter Context Drives Measures

Filter Context → Measure Evaluation FlowSales TableDate Product Amount2024-01-05 Widget A 1502024-01-12 Widget B 2002024-02-03 Widget A 1752024-02-18 Widget B 2252024-03-01 Widget A 300... (more rows)Slicer: ProductSelected: "Widget A"Product = Widget Apropagates filterFiltered Rows2024-01-05 Widget A 1502024-02-03 Widget A 1752024-03-01 Widget A 300Measure ResultsSUM =625AVERAGE =208.33COUNT =3DISTINCTCOUNT =1(only "Widget A")DAX Measure DefinitionsTotal Sales = SUM('Sales'[Amount])Avg Sale = AVERAGE('Sales'[Amount])Num Sales = COUNT('Sales'[Amount])Unique Products = DISTINCTCOUNT('Sales'[Product])
The diagram illustrates how a slicer selection for "Widget A" propagates a filter into the Sales table, reducing it to three matching rows. Each measure—SUM, AVERAGE, COUNT, and DISTINCTCOUNT—evaluates only over the filtered subset, producing context-sensitive results.

This diagram captures the essential mechanism: the filter context determines which rows participate in the aggregation. When the user selects "Widget A" in the slicer, the engine internally restricts the Sales table to only those rows where Product equals "Widget A". The SUM function then adds up the Amount values (150 + 175 + 300 = 625), AVERAGE divides by the count of values (625 ÷ 3 ≈ 208.33), COUNT returns the number of non-blank Amount values (3), and DISTINCTCOUNT returns the number of unique Product values in the filtered set (1). If the user clears the slicer, all rows become visible, and every measure recalculates automatically. This dynamic recalculation without any code changes is the defining advantage of DAX measures over static formulas.

SECTION 4

Function Signatures & Evaluation Semantics

Each of the four core aggregation functions follows a simple signature, but their evaluation semantics differ in important ways. Understanding these differences at a precise level prevents subtle bugs—such as using COUNT when you need COUNTA, or expecting DISTINCTCOUNT to handle blanks the same way COUNT does. The following formalized descriptions specify exactly what each function computes.

SUM

SUM(′Table′[Column])=ΣxiforallxiinfilteredrowswherexiisnotBLANKSUM( 'Table'[Column] ) = Σ xᵢ for all xᵢ in filtered rows where xᵢ is not BLANK

Accepts a single numeric column reference. Returns the arithmetic sum of all non-blank values in the column, restricted to the current filter context. Equivalent to SUMX('Table', 'Table'[Column]) but optimized by the engine. Returns BLANK if no rows pass the filter.

AVERAGE

AVERAGE(′Table′[Column])=(Σxi)/nwheren=countofnon−BLANKxiAVERAGE( 'Table'[Column] ) = ( Σ xᵢ ) / n where n = count of non-BLANK xᵢ

Accepts a single numeric column reference. Computes the arithmetic mean by dividing the sum of non-blank values by the count of non-blank values. Critically, BLANK values are excluded from both the numerator and the denominator, so they do not pull the average toward zero. Returns BLANK if n = 0.

COUNT

COUNT(′Table′[Column])=∣xi:xiisnumericandnotBLANK∣COUNT( 'Table'[Column] ) = | { xᵢ : xᵢ is numeric and not BLANK } |

Accepts a single column reference. Counts only values that can be parsed as numbers—dates are counted (stored internally as floats), but text strings and BLANKs are excluded. For counting text values, use COUNTA. For counting all rows regardless of column, use COUNTROWS('Table').

DISTINCTCOUNT

DISTINCTCOUNT(′Table′[Column])=∣uniquexivaluesincludingBLANKasonevalue∣DISTINCTCOUNT( 'Table'[Column] ) = | { unique xᵢ values including BLANK as one value } |

Accepts a single column reference. Returns the number of distinct values in the column within the filter context. Unlike COUNT, DISTINCTCOUNT includes BLANK as one distinct value if any BLANKs exist. This is semantically equivalent to COUNTROWS(DISTINCT('Table'[Column])) and is often used for cardinality analysis—counting unique customers, unique products, unique sessions, etc.

⚠

BLANK Handling Matters

A common source of bugs: COUNT ignores BLANKs entirely, AVERAGE excludes BLANKs from both numerator and denominator, and DISTINCTCOUNT counts BLANK as one distinct value if present. Always verify BLANK semantics when your data has missing values—this is analogous to the difference between NULL handling in SQL's COUNT(*) vs. COUNT(column).

SECTION 5

Detailed Comparison — SUM, AVERAGE, COUNT, DISTINCTCOUNT

Choosing the right aggregation function requires understanding the data type requirements, BLANK handling, and the precise question each function answers. The table below provides a systematic comparison, and the subsequent diagram visualizes how each function processes the same column of data to produce different results.

FunctionInput TypeBLANK HandlingReturn TypeTypical Use Case
SUMNumeric column onlyIgnores BLANKsDecimal / IntegerTotal revenue, total quantity, cumulative values
AVERAGENumeric column onlyExcludes BLANKs from count and sumDecimalAverage order value, mean score, KPI baselines
COUNTNumeric / Date columnIgnores BLANKs and non-numeric textIntegerNumber of transactions, row counts on numeric fields
DISTINCTCOUNTAny column typeCounts BLANK as one valueIntegerUnique customers, unique products, cardinality checks

Comparison of the four fundamental DAX aggregation functions

Four Functions, One Column — Side-by-Side Processing[Amount]150200BLANK175225300150200BLANKSUMAdds non-BLANK:150+200+175+225+300+150+2001,400BLANKs skipped ✓AVERAGESum / non-BLANK count:1400 / 7200n = 7 (not 9)BLANKs excludedfrom both Σ and n ✓COUNTNumeric, non-BLANK:150,200,175,225300,150,20072 BLANKs skippedDISTINCTCOUNTUnique values:150, 200, 175225, 300, BLANK6BLANK counts asone distinct value ✓Key InsightSame 9-row column → 4 different answers. Understanding BLANK handling is the difference between correct and buggy measures.
All four functions operate on the same nine-value Amount column (including two BLANKs). Notice that SUM returns 1,400, AVERAGE returns 200 (dividing by 7, not 9), COUNT returns 7 (skipping BLANKs), and DISTINCTCOUNT returns 6 (counting BLANK as one distinct value). This side-by-side view makes the BLANK-handling differences immediately visible.

SECTION 6

Worked Example — E-Commerce Sales Dashboard

Consider an e-commerce company with a Orders table containing columns [OrderID], [CustomerID], [OrderDate], [ProductCategory], and [Revenue]. The product manager wants four KPIs on a dashboard: total revenue, average order value, number of orders, and number of unique customers. A slicer is set to ProductCategory = "Electronics". Assume the filtered data contains 250 rows, with Revenue values ranging from $15.99 to $1,299.99, two BLANK Revenue entries, and 187 unique CustomerIDs.

Building Four KPI Measures for an Electronics Dashboard

  1. 1

    Step 1 — Define the Total Revenue Measure

    Navigate to the Modeling tab and click "New Measure" (or right-click the Orders table in the Fields pane). Enter the following DAX formula: Total Revenue = SUM('Orders'[Revenue]). This measure sums all non-BLANK Revenue values across the 250 filtered rows. The two BLANK entries are excluded automatically. With the Electronics filter active, suppose the non-BLANK values sum to $87,432.50.
    Total Revenue = $87,432.50
  2. 2

    Step 2 — Define the Average Order Value Measure

    Create a second measure: Avg Order Value = AVERAGE('Orders'[Revenue]). AVERAGE divides the sum ($87,432.50) by the count of non-BLANK values (248, since two of 250 rows have BLANK Revenue). Note that AVERAGE does not divide by 250—it automatically excludes BLANKs from both numerator and denominator.
    Avg Order Value = $87,432.50 ÷ 248 = $352.55
  3. 3

    Step 3 — Define the Number of Orders Measure

    For counting orders, we have two valid approaches. If we want to count rows with a numeric Revenue: Num Orders = COUNT('Orders'[Revenue]) returns 248. However, if we want to count all orders regardless of whether Revenue is populated, we should use: Num Orders = COUNTROWS('Orders') which returns 250. The business requirement determines which is correct. Since an order exists even if revenue hasn't been recorded yet, COUNTROWS is typically the better choice here.
    Num Orders (COUNTROWS) = 250 | Num Orders (COUNT) = 248
  4. 4

    Step 4 — Define the Unique Customers Measure

    Create the final measure: Unique Customers = DISTINCTCOUNT('Orders'[CustomerID]). This examines all 250 rows, extracts the CustomerID values, removes duplicates, and counts the unique values. Since 187 distinct CustomerIDs appear in the filtered data (some customers ordered multiple times), the result is 187. If any rows had a BLANK CustomerID, DISTINCTCOUNT would count that BLANK as one additional distinct value.
    Unique Customers = 187
  5. 5

    Step 5 — Verify in a Card Visual

    Drag each measure into a separate Card visual on the Power BI report canvas. With the ProductCategory slicer set to "Electronics", the four cards should display: $87,432.50, $352.55, 250, and 187. Now change the slicer to "Clothing"—all four values update instantly because each measure re-evaluates within the new filter context. This confirms the measures are dynamic and context-aware, requiring zero code changes when the filter changes.
    All four KPI cards update dynamically when the slicer changes.

SECTION 7

Strengths, Limitations & Common Pitfalls

These four aggregation functions are the workhorses of nearly every Power BI report, but like any abstraction, they carry both strengths and limitations that a competent developer must understand. The following comparison outlines where each function excels and where it can lead to incorrect results if misapplied.

AspectStrengthLimitation / Pitfall
SUMHighly optimized by VertiPaq; near-instantaneous even on billions of rows. Simple, unambiguous semantics.Cannot sum across multiple columns (e.g., SUM(A + B) is invalid). Use SUMX for row-level expressions. Returns BLANK (not 0) when no rows match, which can cause unexpected visual behavior.
AVERAGEAutomatically handles BLANKs correctly for most statistical use cases. No need to manually compute SUM/COUNT.Not a weighted average—if you need revenue-per-unit, AVERAGE won't weight by quantity. Use DIVIDE(SUM([Revenue]), SUM([Quantity])) instead. Susceptible to Simpson's paradox in grouped contexts.
COUNTFast and straightforward for numeric/date columns. Useful for data quality checks (comparing COUNT vs. COUNTROWS reveals BLANK prevalence).Silently ignores text values—COUNT on a text column returns 0, not an error. Developers often confuse COUNT, COUNTA, COUNTROWS, and COUNTBLANK. Choose deliberately.
DISTINCTCOUNTEssential for cardinality analysis. Works on any data type. Highly optimized in VertiPaq due to dictionary encoding.Counts BLANK as a distinct value, which can inflate counts by 1 if not anticipated. Cannot apply conditions inline—use CALCULATE + DISTINCTCOUNT for filtered distinct counts.

Strengths and pitfalls of basic DAX aggregation functions

✦

KEY TAKEAWAY

Think of these four functions as the CRUD operations of analytics—individually simple, but the foundation on which every complex operation is built. Just as a software engineer must understand when to use INSERT vs. UPSERT vs. MERGE, a BI developer must understand when to use COUNT vs. COUNTA vs. COUNTROWS vs. DISTINCTCOUNT. The function you choose encodes an assumption about your data's structure and quality; a wrong choice produces silently incorrect results rather than an explicit error. Always validate measures against known test data before deploying to production.

SECTION 8

Connection to Advanced DAX — Iterator Functions & CALCULATE

The four aggregation functions covered in this lesson are all simple aggregators that accept a single column reference and operate within the existing filter context. As your reporting requirements grow more complex, you will encounter two major extensions: iterator functions (SUMX, AVERAGEX, COUNTAX, etc.) that evaluate a row-level expression before aggregating, and the CALCULATE function that modifies the filter context before a measure evaluates. Understanding basic measures is a strict prerequisite for both.

Basic Measure (This Lesson)Advanced ExtensionWhen You Need It
SUM('T'[Col])SUMX('T', [Col1] × [Col2])When the value to sum is a row-level expression (e.g., Quantity × UnitPrice)
AVERAGE('T'[Col])AVERAGEX('T', expr)When you need the average of a computed expression, not a stored column
COUNT('T'[Col])CALCULATE(COUNT('T'[Col]), filter)When you need to count within a modified filter context (e.g., count only where Status = 'Completed')
DISTINCTCOUNT('T'[Col])CALCULATE(DISTINCTCOUNT('T'[Col]), ALL('T'[Region]))When you need distinct count ignoring certain filters (e.g., total unique customers regardless of region slicer)

How basic measures evolve into advanced patterns

The conceptual leap from basic measures to CALCULATE-based measures is analogous to the leap from writing simple SQL aggregations to writing correlated subqueries or window functions: the building blocks are the same, but the evaluation scope becomes parameterized. Mastering SUM, AVERAGE, COUNT, and DISTINCTCOUNT gives you the mental model of how DAX aggregation and filter context interact, which is the single most important concept for all subsequent DAX learning. In upcoming lessons, you will see how CALCULATE wraps these basic functions to create time intelligence calculations (year-over-year growth), conditional aggregations (revenue only for completed orders), and cross-filter patterns (total regardless of a slicer).

SECTION 9

Practice Problems

The following five problems test your understanding of SUM, AVERAGE, COUNT, and DISTINCTCOUNT at escalating levels of difficulty. For problems involving data, assume an Orders table with columns [OrderID] (integer), [CustomerID] (text), [Region] (text), [Revenue] (decimal), and [Quantity] (integer).

Question 1: CONCEPTUAL

Explain why AVERAGE('Orders'[Revenue]) might return a different result than DIVIDE(SUM('Orders'[Revenue]), COUNTROWS('Orders')). Under what data conditions would they produce the same result?
Show answer
AVERAGE excludes BLANK values from both the numerator (sum) and the denominator (count), effectively computing sum-of-non-blanks ÷ count-of-non-blanks. The DIVIDE version, however, divides the sum of non-blank Revenue values by the total number of rows (including rows where Revenue is BLANK). If the Revenue column contains any BLANK values, AVERAGE's denominator will be smaller than COUNTROWS, producing a higher average. The two expressions yield identical results only when every row in the current filter context has a non-BLANK Revenue value. This distinction is critical in datasets with incomplete data—choosing the wrong version silently biases your KPI.

Question 2: BASIC CALCULATION

Given 8 filtered rows with Revenue values [50, 75, BLANK, 100, 50, 200, 75, BLANK], compute the result of each: (a) SUM('Orders'[Revenue]), (b) AVERAGE('Orders'[Revenue]), (c) COUNT('Orders'[Revenue]), (d) DISTINCTCOUNT('Orders'[Revenue]).
Show answer
(a) SUM adds non-BLANK values: 50 + 75 + 100 + 50 + 200 + 75 = 550. (b) AVERAGE divides 550 by the count of non-BLANK values, which is 6: 550 ÷ 6 ≈ 91.67. (c) COUNT counts non-BLANK numeric values: 6. (d) DISTINCTCOUNT identifies unique values including BLANK: the distinct set is {50, 75, 100, 200, BLANK} = 5 distinct values. Note that 50 appears twice and 75 appears twice, but each is counted only once in DISTINCTCOUNT, and BLANK is counted as one additional distinct value.

Question 3: INTERMEDIATE

A report has a matrix visual with Region on rows and Year on columns. The measure Total Revenue = SUM('Orders'[Revenue]) is placed in the values area. Explain precisely what value appears in the cell at the intersection of Region = "West" and Year = "2024", and what value appears in the "Total" row for the 2024 column. How does the filter context differ between these two cells?
Show answer
The cell at Region = 'West' and Year = '2024' evaluates SUM('Orders'[Revenue]) within a filter context that includes two active filters: Region = 'West' AND Year = '2024'. Only rows satisfying both conditions contribute to the sum. The 'Total' row for the 2024 column has a filter context containing only Year = '2024'—the Region filter is removed (since the Total row aggregates across all regions). Therefore, the Total row sums all Revenue for 2024 regardless of Region. The key insight is that the same measure definition produces different values because the matrix visual programmatically sets different filter contexts for each cell. This is the fundamental principle of DAX evaluation: measures are context-dependent.

Question 4: APPLIED

You are building a customer analytics dashboard. The product owner asks for a KPI card showing 'Average Revenue per Customer.' Your first attempt is AVERAGE('Orders'[Revenue]), but this returns the average revenue per order, not per customer. Write a correct DAX measure using only the basic functions covered in this lesson (SUM, AVERAGE, COUNT, DISTINCTCOUNT) combined with DIVIDE. Explain why your solution works.
Show answer
The correct measure is: Avg Revenue Per Customer = DIVIDE(SUM('Orders'[Revenue]), DISTINCTCOUNT('Orders'[CustomerID])). This works because SUM('Orders'[Revenue]) computes total revenue across all filtered orders, and DISTINCTCOUNT('Orders'[CustomerID]) counts the number of unique customers who placed those orders. Dividing total revenue by unique customer count gives the average revenue per customer. Using AVERAGE('Orders'[Revenue]) would divide by the number of orders, not the number of customers—a customer who placed 5 orders would be counted 5 times in the denominator, deflating the per-customer average. DIVIDE is preferred over the / operator because it returns BLANK (not an error) when the denominator is zero, which is important for filter combinations that might have no customers.

Question 5: CRITICAL THINKING

Consider a scenario where DISTINCTCOUNT('Orders'[CustomerID]) returns 1,002 when no filters are applied, but COUNTROWS(DISTINCT('Orders'[CustomerID])) returns 1,001. Provide a precise explanation for the discrepancy. Then, discuss whether this difference would affect a production dashboard and how you would investigate and resolve it.
Show answer
DISTINCTCOUNT counts BLANK as one distinct value, while the COUNTROWS(DISTINCT(...)) pattern may also count BLANK, so the typical explanation for a difference of exactly 1 is BLANK handling. Specifically, DISTINCTCOUNT is documented as equivalent to COUNTROWS(DISTINCT(...)), so a discrepancy of 1 suggests there is one row with a BLANK CustomerID, and DISTINCTCOUNT counts it as a distinct value while something in the COUNTROWS(DISTINCT()) evaluation path handles it differently (possibly due to a model-level property or relationship filtering). In practice, the first step is to investigate with COUNTBLANK('Orders'[CustomerID]) to confirm BLANK rows exist. On a production dashboard, this matters: if 'Unique Customers' is reported as 1,002 but only 1,001 real customers exist, the extra BLANK inflates the metric. The resolution is either to clean the data (ensure no BLANK CustomerIDs) or to wrap the measure in CALCULATE with a filter removing BLANKs: CALCULATE(DISTINCTCOUNT('Orders'[CustomerID]), NOT(ISBLANK('Orders'[CustomerID]))). This is a real-world data quality issue that highlights why understanding BLANK semantics is not merely academic.

SUMMARY

Summary — Basic DAX Measures

Summary

DAX measures are named formulas evaluated at query time within the current filter context, making them inherently dynamic and responsive to user interactions like slicers and visual groupings. The four foundational aggregation functions— SUM (arithmetic total of a numeric column), AVERAGE (arithmetic mean excluding BLANKs from both numerator and denominator), COUNT (number of non-BLANK numeric values), and DISTINCTCOUNT (number of unique values, counting BLANK as one)—form the building blocks upon which all advanced DAX patterns are constructed.

The critical distinctions between these functions center on BLANK handling: SUM and COUNT ignore BLANKs, AVERAGE excludes them from both components of the division, and DISTINCTCOUNT treats BLANK as a countable distinct value. Choosing the wrong function produces silently incorrect results rather than errors, making deliberate function selection essential. These basic measures extend naturally into iterator functions (SUMX, AVERAGEX) for row-level expressions and CALCULATE for modifying the filter context, forming the pathway to time intelligence, conditional aggregation, and advanced analytical patterns in Power BI.

Varsity Tutors • Microsoft Power BI • Basic DAX Measures — Write basic measures using SUM, AVERAGE, COUNT, DISTINCTCOUNT