You write a measure with SUM, and it works. Then someone asks for revenue before discounts, and SUM suddenly can't help. You switch to SUMX, the numbers appear, and you are left wondering why one function worked and the other didn't. If that sounds familiar, you are in good company. The SUM vs SUMX question trips up beginners and experienced developers alike.
The short version: SUM adds up one column, and SUMX evaluates an expression for every row of a table and then adds up the results. This article explains that difference in plain language, shows when each function is the right choice, and walks through 20 examples from beginner to advanced. Along the way we will cover the DAX SUMX function, row context in DAX, context transition, and SUM vs SUMX performance.
The Sample Data Model Used in This Article
Every example uses the same small star schema, so you can focus on the DAX instead of learning a new model each time.
- Customer (dimension): CustomerKey, CustomerName, Segment, City, Country, Region, and more.
- Product (dimension): ProductKey, ProductName, Category, Subcategory, UnitCost, ListPrice, and more.
- Date (dimension, marked as the date table): Date, Year, MonthNumber, MonthName, MonthYear, MonthYearSort, and more.
- Sales (fact, one row per order): SalesOrderID, CustomerKey, OrderDate, ShipDate, SalesChannel, Freight, TaxAmount.
- Sales Order Details (fact, one row per product per order): SalesOrderID, ProductKey, Quantity, UnitPrice, DiscountPct, SalesAmount, TotalCost.
All relationships are one-to-many and single direction. Customer and Date filter Sales, and Sales filters Sales Order Details. Product filters Sales Order Details directly. The Date to Sales[OrderDate] relationship is active, and Date to Sales[ShipDate] is inactive.
Important: Freight and TaxAmount live on the order header (Sales). Product does not filter Sales, so you should never slice header-level amounts by Product columns. We will flag this again when it matters.
These are the base measures reused throughout the article:
Total Sales =
SUM ( 'Sales Order Details'[SalesAmount] )
Total Quantity =
SUM ( 'Sales Order Details'[Quantity] )
Total Freight =
SUM ( Sales[Freight] )
Every other measure is introduced right before its example. The thresholds, rates, and caps in the examples are illustrative business assumptions, not columns in the model.
Understanding the Concepts
SUM: An Aggregator Over a Single Column
SUM takes one column and adds up its values in the current filter context. That is all it does. It cannot multiply two columns, look at a related table, or apply an IF to each row. Conceptually, you can think of it as a shortcut for summing one column of a table.
SUM ( <ColumnName> )
SUMX: An Iterator That Evaluates Row by Row
SUMX is one of the DAX iterator functions. It takes a table and an expression. For each row of that table, it evaluates the expression, then adds up all the results. Other members of the iterator family include AVERAGEX, MINX, MAXX, COUNTX, and RANKX. They all share the same pattern: a table first, an expression second.
SUMX ( <Table>, <Expression> )
Row Context in DAX
Row context means "the current row." When SUMX walks through a table, it creates a row context for each row, and column references in the expression return that row's values. This is why 'Sales Order Details'[Quantity] * 'Sales Order Details'[UnitPrice] works inside SUMX: for each line, it multiplies that line's quantity by that line's price.
Outside an iterator, a bare column reference in a measure has no row context, so DAX does not know which row you mean. SUM avoids the problem because it aggregates the whole column in the filter context instead of reading one row at a time.
Context Transition Inside SUMX
Here is a subtle but important point. A plain column reference inside an iterator uses row context. A measure reference inside an iterator behaves differently, because every measure is wrapped in an implicit CALCULATE. CALCULATE converts the current row into filters, and this is called context transition.
For example, if you iterate over the Customer table and call [Total Sales], DAX filters the model to the current customer before evaluating the measure. That is how you get per-customer results inside one measure. It is powerful, and it is also where performance problems usually start, since the measure is re-evaluated for every iterated row.
When to Choose SUM and When SUMX Is Required
Choose SUM When
- You are adding up a single column that already holds the value you need, such as SalesAmount, Quantity, or Freight.
- Any filtering you need can be handled by CALCULATE or by slicers.
SUMX Is Required When
- Calculations span multiple columns: Quantity * UnitPrice, or SalesAmount - TotalCost evaluated line by line.
- You need columns from related tables: for example, multiplying Quantity by Product[ListPrice] using RELATED.
- Row-level conditional logic applies: different treatment for each row, such as IF or SWITCH.
- You want a custom grain: compute something per category, per customer, or per product first, then add the results.
SUM vs SUMX: Comparison Table
| Aspect | SUM | SUMX |
|---|---|---|
| Purpose | Add up the values of one column | Evaluate an expression per row, then add the results |
| Input | A single column | A table and an expression |
| Row context | None created | Created for each row of the table |
| Flexibility | Low: one column only | High: multiple columns, related tables, logic, custom grain |
| Typical performance | Very fast and simple | Often fast for simple column math; slower when measures or complex logic run per row |
Rule of thumb: use SUM when it can do the job, and reach for SUMX when you need a calculation at row level or at a custom grain.
20 SUM and SUMX Examples
Beginner Examples (1-6)
Example 1: Basic SUM
Scenario: Management wants total revenue.
Total Sales =
SUM ( 'Sales Order Details'[SalesAmount] )
This is our base measure. SalesAmount already holds Quantity x UnitPrice x (1 - DiscountPct) for every line, so a plain SUM is enough. Slicers on Customer, Product, or Date filter it automatically. Whenever a stored column already contains the number you need, start here.
Example 2: SUM with a CALCULATE Filter
Scenario: The sales director wants revenue from Corporate customers only.
Corporate Sales =
CALCULATE ( [Total Sales], Customer[Segment] = "Corporate" )
CALCULATE changes the filter context before [Total Sales] runs. The filter on Customer flows through Sales into Sales Order Details, so no iteration is needed. This is the standard way to apply a fixed filter to a SUM-based measure.
Example 3: SUM of Quantity
Scenario: The warehouse team wants to know how many units were sold.
Total Quantity =
SUM ( 'Sales Order Details'[Quantity] )
Another simple aggregation over one column. Units sold are a different story from revenue, so keeping a separate Total Quantity measure makes later ratios easier. We reuse it in example 17.
Example 4: SUM of Freight
Scenario: Finance wants to track shipping costs.
Total Freight =
SUM ( Sales[Freight] )
Freight sits on the order header, one row per order. Watch out: Product does not filter Sales, so putting Product[Category] on a visual next to Total Freight repeats the same overall freight on every row. Slice freight by Customer, Date, or SalesChannel instead.
Example 5: A SUMX That Matches SUM
Scenario: You want to see that SUMX with a single column reproduces SUM.
Total Sales (SUMX) =
SUMX ( 'Sales Order Details', 'Sales Order Details'[SalesAmount] )
SUMX visits each order line, reads its SalesAmount, and adds the values, so the result equals Total Sales. This example is for learning only. The engine often handles both forms efficiently, but SUM states your intent more clearly and is the better choice in production.
Example 6: First Real SUMX with Quantity x UnitPrice
Scenario: The pricing team asks how much revenue we would have earned with no discounts.
Gross Sales =
SUMX (
'Sales Order Details',
'Sales Order Details'[Quantity] * 'Sales Order Details'[UnitPrice]
)
No single column holds quantity times price, so SUM cannot do this. SUMX creates a row context for each line, multiplies the two columns, and adds the products. Summing Quantity and UnitPrice separately and multiplying the totals would give a wrong answer. This is the classic reason SUMX exists.
Intermediate Examples (7-14)
Example 7: Discount Amount
Scenario: Leadership wants to know how much revenue was given away in discounts.
Discount Amount =
SUMX (
'Sales Order Details',
'Sales Order Details'[Quantity]
* 'Sales Order Details'[UnitPrice]
* 'Sales Order Details'[DiscountPct]
)
Discount % of Gross Sales =
DIVIDE ( [Discount Amount], [Gross Sales] )
Each line's discount is quantity x price x discount percentage, summed across lines. As a sanity check, [Gross Sales] - [Total Sales] should return the same figure. The second measure expresses the discount as a share of gross sales, using DIVIDE to avoid divide-by-zero errors.
Example 8: SUMX with RELATED
Scenario: What would revenue be if every unit sold at today's list price?
Sales at List Price =
SUMX (
'Sales Order Details',
'Sales Order Details'[Quantity] * RELATED ( Product[ListPrice] )
)
ListPrice lives in Product, not in the fact table. RELATED follows the many-to-one relationship from each order line to its product and fetches the price. RELATED only works inside a row context, which SUMX provides. Comparing this measure with Gross Sales shows how far historical prices drifted from the current price list.
Example 9: SUMX with IF per Row
Scenario: A 5% volume rebate applies to order lines of 10 units or more.
Net Sales After Volume Rebate =
SUMX (
'Sales Order Details',
'Sales Order Details'[SalesAmount]
* IF ( 'Sales Order Details'[Quantity] >= 10, 0.95, 1 )
)
The condition is evaluated separately for each line, which is something SUM cannot do. Lines with 10 or more units keep 95% of their sales amount, and all other lines keep 100%. The 5% rate and the threshold of 10 are made-up business rules for this example.
Example 10: SUMX over a Filtered Table
Scenario: Sales wants revenue from large order lines only, those worth at least 1,000.
High-Value Line Sales =
SUMX (
FILTER (
'Sales Order Details',
'Sales Order Details'[SalesAmount] >= 1000
),
'Sales Order Details'[SalesAmount]
)
FILTER returns only the lines that pass the test, and SUMX adds up the survivors. The condition compares a value on each row, which a simple CALCULATE filter cannot express as neatly. Be aware that FILTER over a large fact table can be costly, so use it only when the row-level test is genuinely needed.
Example 11: SUMX over VALUES(Product[Category])
Scenario: Commission is paid on category sales, but each category is capped at 500,000.
Commissionable Sales (Capped per Category) =
SUMX (
VALUES ( Product[Category] ),
VAR CategorySales = [Total Sales]
RETURN
IF ( CategorySales > 500000, 500000, CategorySales )
)
VALUES returns the categories visible in the current filter context, giving us a custom grain. For each category, context transition makes [Total Sales] return that category's sales, and the IF applies the cap. The results are then added. The key point is that the cap applies per category, then the totals are added, so the grand total is not capped as a whole.
Example 12: Profit per Line at Standard Cost
Scenario: Finance wants profit if each unit had cost today's standard UnitCost.
Profit at Standard Cost =
SUMX (
'Sales Order Details',
VAR StandardCost =
'Sales Order Details'[Quantity] * RELATED ( Product[UnitCost] )
RETURN
'Sales Order Details'[SalesAmount] - StandardCost
)
The variable calculates the standard cost of each line, and the expression subtracts it from that line's SalesAmount. Our base Total Profit uses the stored TotalCost column instead, so this measure answers a different question. Variables keep row-level logic readable and avoid repeating the same calculation.
Example 13: Channel-Based Commission with RELATED on the Header
Scenario: Partners and retail stores earn different commission rates than online sales.
Estimated Channel Commission =
SUMX (
'Sales Order Details',
'Sales Order Details'[SalesAmount]
* SWITCH (
RELATED ( Sales[SalesChannel] ),
"Online", 0.02,
"Retail", 0.03,
"Partner", 0.08,
0
)
)
SalesChannel lives on the order header, but each order line can reach it through RELATED. SWITCH picks a rate for each line based on its channel, and SUMX adds the commission amounts. The rates are illustrative. This pattern works whenever a line-level amount needs a rule driven by a header attribute.
Example 14: Average Monthly Sales with AVERAGEX
Scenario: Executives want the average sales per month for whatever period is selected.
Average Monthly Sales =
AVERAGEX (
VALUES ( 'Date'[MonthYear] ),
[Total Sales]
)
AVERAGEX is SUMX's sibling: it evaluates an expression per row and averages the results instead of adding them. Here the rows are the months in the current filter context, and each month's [Total Sales] comes from context transition. Months with no sales return blank and are ignored by AVERAGEX, which is usually what you want for an average.
Advanced Examples (15-20)
Example 15: SUMX over Customer with a Threshold
Scenario: Count only the revenue from key accounts, customers who spent more than 10,000.
Key Account Sales =
SUMX (
Customer,
VAR CustomerSales = [Total Sales]
RETURN
IF ( CustomerSales > 10000, CustomerSales, 0 )
)
Iterating over the Customer table gives us one row per customer. For each customer, context transition computes their total sales, and the IF keeps only those above the threshold. The grain here is the customer, which cannot be reached with a simple SUM. The 10,000 threshold is an assumption for illustration.
Example 16: SUMX over SUMMARIZE
Scenario: Show sales only from category and channel combinations that exceeded 50,000.
Sales in Strong Category-Channel Combinations =
SUMX (
FILTER (
SUMMARIZE (
'Sales Order Details',
Product[Category],
Sales[SalesChannel]
),
[Total Sales] > 50000
),
[Total Sales]
)
SUMMARIZE builds a small table with one row per category and channel combination that exists in the data. FILTER keeps the combinations above 50,000, and SUMX adds their sales. Because the iterated table is small, this is far cheaper than looping over millions of order lines. Both measures inside the iterator rely on context transition.
Example 17: Weighted Average Price
Scenario: What is the true average unit price, taking quantities into account?
Weighted Average Unit Price =
DIVIDE (
SUMX (
'Sales Order Details',
'Sales Order Details'[Quantity] * 'Sales Order Details'[UnitPrice]
),
[Total Quantity]
)
A simple average of UnitPrice treats a line of 1 unit the same as a line of 500 units. The weighted version divides total price-times-quantity by total units, so large lines count for more. This is the price before discounts. It is the same as dividing [Gross Sales] by [Total Quantity].
Example 18: SUMX with TOPN
Scenario: How much of our revenue comes from the five best-selling products?
Top 5 Products Sales =
SUMX (
TOPN ( 5, VALUES ( Product[ProductName] ), [Total Sales] ),
[Total Sales]
)
TOPN ranks the visible products by [Total Sales] and returns the top five. SUMX then adds up their sales, using context transition for each product. If two products tie for fifth place, TOPN can return more than five rows, so keep that in mind. Divide this by [Total Sales] if you want the top-five share.
Example 19: Running Total
Scenario: Show cumulative sales from the start of the data up to each date.
Running Total Sales =
VAR LastVisibleDate = MAX ( 'Date'[Date] )
RETURN
SUMX (
FILTER ( ALL ( 'Date' ), 'Date'[Date] <= LastVisibleDate ),
[Total Sales]
)
The variable captures the latest date in the current context. ALL removes the existing date filters, FILTER keeps every date up to that point, and SUMX adds up each day's sales. Because ALL ignores date slicers, this measure always starts from the beginning of the data. It iterates over every day, so test it on large date ranges.
Example 20: A Performance-Aware Rewrite
Scenario: An existing report calculates online revenue by iterating over every order line.
-- Before: iterates the large fact table and checks the channel on every row
Online Sales (Slow) =
SUMX (
FILTER (
'Sales Order Details',
RELATED ( Sales[SalesChannel] ) = "Online"
),
'Sales Order Details'[SalesAmount]
)
-- After: a simple filter on a column and a plain SUM
Online Sales =
CALCULATE ( [Total Sales], Sales[SalesChannel] = "Online" )
Both versions return the same result. The first walks the large fact table and tests each line, while the second applies a column filter and lets the engine do a simple aggregation. The same logic applies to anything that recomputes a stored column, such as multiplying Quantity, UnitPrice, and 1 - DiscountPct when SalesAmount already holds that value. If SUM and CALCULATE can do the job, prefer them.
Common Mistakes and Performance Tips
- Using SUMX when SUM is enough. SUMX over a single column, as in example 5, adds complexity with no benefit. Use SUM for a plain column total.
- Iterating over a large fact table when a smaller table would work. If you only need results per category, iterate over VALUES ( Product[Category] ) instead of every order line, as in examples 11 and 16.
- Calling measures inside an iterator over a big table. Context transition runs once per row. Iterating over Sales Order Details with a measure inside can be slow, so prefer simple column math there and keep measure-based iteration for small tables.
- Using FILTER over a whole fact table for a simple condition. A column filter inside CALCULATE is usually lighter than FILTER ( 'Sales Order Details', ... ), as example 20 shows.
- Forgetting how totals behave. A SUMX over VALUES applies logic per item, then adds. Grand totals are the sum of the item results, not a recalculation on the whole, which matters for caps and thresholds. Also remember that header-level amounts like Freight are not filtered by Product.
Conclusion: Key Takeaways
The SUM vs SUMX choice comes down to one question: does a column already hold the value you want to add up?
- SUM adds up one column in the current filter context. It is simple, readable, and fast.
- SUMX is an iterator. It creates a row context, evaluates an expression for each row, and adds the results.
- Use SUMX for multi-column math, related-table columns via RELATED, row-level logic, and custom grains.
- Measures inside an iterator trigger context transition, which is powerful but costly on large tables.
- Iterate over the smallest table that gives the right grain, and rewrite SUMX as SUM with CALCULATE whenever possible.
Once you can tell when you need a row context and when you don't, most of the confusion around iterators goes away.
Want to Go Deeper?
Ready to master DAX, data modeling, and SSAS Tabular? Explore more step-by-step tutorials, practical examples, and best-practice guides at exceltobi.com.