If you’ve spent time with SUMMARIZE() and SUMMARIZECOLUMNS() in Power BI DAX, you might wonder why GROUPBY() even exists. On the surface, all three seem to do the same thing — group a table and compute aggregations.
But Power BI GROUPBY() has a fundamentally different engine under the hood, and once you understand what makes it unique, you’ll know exactly when to reach for it — and how to combine it with FILTER() to solve problems that SUMMARIZE() simply cannot handle cleanly.
This tutorial covers how to use Power BI DAX GROUPBY with FILTER across real business scenarios, and gives you the full picture of where it fits in your DAX toolkit.
What Is GROUPBY() in Power BI DAX?
Power BI DAX GROUPBY() is a table function that groups a source table by one or more columns and returns a summary table, one row per unique group. Here’s the syntax:
GROUPBY(
<table>,
[<groupBy_column1>],
[<groupBy_column2>],
...
<name>, <expression>
)

So far, this sounds identical to SUMMARIZE(). The critical difference is in how aggregation expressions inside GROUPBY() work. GROUPBY() does not perform an implicit CALCULATE and does not transition row context into filter context the way SUMMARIZE() does. Instead, it gives you access to each group’s rows through a special function called CURRENTGROUP().
This distinction is everything. Let me show you what it means in practice.
Before writing any DAX, let’s import the Excel file into Power BI.
- Open Power BI Desktop
- Click Get Data
- Choose Excel
- Select the sample Excel file Load:
- Sales Data
- Customers table

Power BI CURRENTGROUP() — The Heart of GROUPBY()
CURRENTGROUP() is a function that works only within Power BI GROUPBY(). It returns the subset of rows from the source table that belong to the current group being iterated. You then pass CURRENTGROUP() into an iterator function SUMX(), AVERAGEX(), MAXX(), MINX(), and COUNTX() to compute your aggregation.
Regional Revenue = GROUPBY(
'Sales Data',
'Sales Data'[Region],
"Total Revenue", SUMX(CURRENTGROUP(), 'Sales Data'[Revenue])
)

SUMX() here receives the current group’s rows via CURRENTGROUP() and sums their Revenue values. The result is a table with one row per Region and a Total Revenue column.
Compare this to SUMMARIZE() + ADDCOLUMNS():
-- SUMMARIZE approach
ADDCOLUMNS(
SUMMARIZE(Sales, Sales[Region]),
"Total Revenue", CALCULATE(SUM(Sales[Revenue]))
)
The SUMMARIZE approach uses CALCULATE(), which performs a context transition and evaluates SUM(Sales[Revenue]) in the filter context of the current Region. GROUPBY() avoids that context transition entirely. It works purely at the row level within each group in Power BI.
Why GROUPBY() Exists in Power BI: The Critical Use Case
Here’s where GROUPBY() becomes genuinely irreplaceable. When your source table is a virtual table with no column lineage — meaning a table created by DAX functions like ADDCOLUMNS(), SELECTCOLUMNS(), or UNION() that don’t have direct ties back to physical model columns — CALCULATE() doesn’t know how to establish filter context. Context transitions fail or produce wrong results.
GROUPBY() + CURRENTGROUP() doesn’t need column lineage or context transitions. It works with any table you hand it, making it the only reliable way to group and aggregate virtual tables.
This is the specific scenario Power BI GROUPBY() was designed for:
VAR TaggedCustomers =
ADDCOLUMNS(
VALUES(Customer[CustomerID]),
"SpendCategory",
IF([Total Sales] >= 1000, "High Value", "Standard")
)
-- TaggedCustomers is a virtual table with no lineage on SpendCategory
VAR SpendSummary =
GROUPBY(
TaggedCustomers,
[SpendCategory],
"CustomerCount", COUNTX(CURRENTGROUP(), Customer[CustomerID])
)
RETURN
SpendSummary
You cannot use SUMMARIZE() here because SpendCategory is a computed column in a virtual table — it has no lineage, no physical model backing. GROUPBY() handles it perfectly.
Power BI FILTER() + GROUPBY() — The Core Combination
Now let’s get into the main topic: combining Power BI FILTER() with GROUPBY(). There are two distinct ways to do this, and both are useful in different scenarios.
Method 1: FILTER the Input Table Before GROUPBY() in Power BI
The first approach is to filter your source data before it enters Power BI GROUPBY(). This is the more performance-friendly pattern — you reduce the number of rows being grouped before the grouping happens.
Revenue 2025 =
GROUPBY(
FILTER('Sales Data', 'Sales Data'[Year] = 2025),
'Sales Data'[Region],
"Revenue", SUMX(CURRENTGROUP(), 'Sales Data'[Revenue])
)

The inner FILTER() narrows Sales to 2025 rows only. GROUPBY() then groups the already-reduced dataset by Region. The result is a summary table of 2025 revenue per region.
Method 2: FILTER the Output of GROUPBY() in Power BI
The second approach builds the full summary first and then filters the resulting grouped table. Use this when your filter condition depends on an aggregated value — something that doesn’t exist at the row level.
High Revenue Regions =
FILTER(
GROUPBY(
'Sales Data',
'Sales Data'[Region],
"Revenue", SUMX(CURRENTGROUP(), 'Sales Data'[Revenue])
),
[Revenue] > 1000
)

Here, you can’t apply Revenue > 100000 before grouping — Revenue is only known after aggregation. So you let GROUPBY() build the summary first, then Power BI FILTER() keeps only the high-revenue regions.
Power BI DAX GROUPBY With FILTER
Now discuss some of the real world examples.
Example 1: Regional Revenue Analysis in Power BI
Business scenario: Your VP of Sales wants a count of how many regions exceeded $500 in revenue in 2025.
High Revenue Region Count =
VAR RegionalRevenue =
GROUPBY(
FILTER(
'Sales Data',
YEAR('Sales Data'[OrderDate]) = 2025
),
'Sales Data'[Region],
"Revenue",
SUMX(
CURRENTGROUP(),
'Sales Data'[Revenue]
)
)
VAR TopRegions =
FILTER(
RegionalRevenue,
[Revenue] > 500
)
RETURN
COUNTROWS(TopRegions)

The VAR structure makes the logic easy to follow. RegionalRevenue holds the summary. TopRegions filters it to the qualifying regions. COUNTROWS() delivers the final answer.
Example 2: Grouping a Virtual Table in Power BI (Where GROUPBY Shines)
This is the scenario where Power BI GROUPBY() is not just convenient — it’s the right tool and SUMMARIZE() would be unreliable.
Business scenario: You want to classify customers as “High Value” (total purchases > $500) or “Standard,” then count how many customers fall into each category.
High Revenue Region Count =
VAR RegionalRevenue =
GROUPBY(
FILTER(
'Sales Data',
YEAR('Sales Data'[OrderDate]) = 2025
),
'Sales Data'[Region],
"Revenue",
SUMX(
CURRENTGROUP(),
'Sales Data'[Revenue]
)
)
VAR TopRegions =
FILTER(
RegionalRevenue,
[Revenue] > 500
)
RETURN
COUNTROWS(TopRegions)

Because both TotalPurchases and Tier exist only as computed columns in virtual tables, GROUPBY() + CURRENTGROUP() is the only reliable aggregation approach here. SUMMARIZE() with Power BI ADDCOLUMNS() would work for simple cases, but GROUPBY() is more semantically correct and safer for virtual table grouping.
Example 3: Power BI Conditional Aggregation Inside GROUPBY()
One of the most powerful capabilities of CURRENTGROUP() is that you can apply conditions inside your aggregation expressions — effectively computing filtered aggregates per group.
Business scenario: For each product category, you want to know both the total revenue AND the revenue from orders above $300 only (your “premium order” threshold).
Category Revenue Breakdown =
GROUPBY(
'Sales Data',
'Sales Data'[Category],
"Total Revenue",
SUMX(
CURRENTGROUP(),
'Sales Data'[Revenue]
),
"Premium Order Revenue",
SUMX(
CURRENTGROUP(),
IF(
'Sales Data'[Revenue] > 300,
'Sales Data'[Revenue],
0
)
),
"Premium Order Count",
SUMX(
CURRENTGROUP(),
IF(
'Sales Data'[Revenue] > 300,
1,
0
)
)
)

Notice what’s happening in “Premium Order Revenue”: FILTER(CURRENTGROUP(), Sales[Revenue] > 300) filters the current group’s rows to only those with Revenue above 300, and SUMX() aggregates that filtered subset. This is conditional aggregation within groups — all in a single GROUPBY() call.
This is genuinely difficult to achieve cleanly with SUMMARIZE() and would require multiple separate measures or multiple Power BI ADDCOLUMNS() calls.
Example 4: Multi-Column Grouping with Downstream FILTER() in Power BI
Business scenario: Your operations team needs to identify Region + Salesperson combinations where the average deal size dropped below $200, and they want the total revenue attributed to those underperforming combinations.
Underperforming Combo Revenue =
VAR RepRegionStats =
GROUPBY(
'Sales Data',
'Sales Data'[Region],
'Sales Data'[SalesRepName],
"AvgDeal", AVERAGEX(CURRENTGROUP(), 'Sales Data'[Revenue]),
"TotalRevenue", SUMX(CURRENTGROUP(), 'Sales Data'[Revenue])
)
VAR UnderperformingCombos =
FILTER(
RepRegionStats,
[AvgDeal] < 400
)
RETURN
SUMX(
UnderperformingCombos,
[TotalRevenue]
)

The GROUPBY() creates a per-combination summary. FILTER() isolates underperforming combos. TREATAS() maps those combinations back to the physical Sales table for the final revenue calculation.
Example 5: Using GROUPBY to Tag and Re-Summarize
This is the classic GROUPBY() pattern from Power BI DAX Guide — tagging rows with a computed category and then re-grouping by that tag.
Business scenario: You want to know how many customers are “above average” buyers versus “below average,” based on total purchases compared to the company-wide average.
Category Revenue Breakdown =
GROUPBY(
'Sales Data',
'Sales Data'[Category],
"Total Revenue",
SUMX(
CURRENTGROUP(),
'Sales Data'[Revenue]
),
"Premium Order Revenue",
SUMX(
CURRENTGROUP(),
IF(
'Sales Data'[Revenue] > 300,
'Sales Data'[Revenue],
0
)
),
"Premium Order Count",
SUMX(
CURRENTGROUP(),
IF(
'Sales Data'[Revenue] > 300,
1,
0
)
)
)

This is a two-stage summarization — first by CustomerID to get individual totals, then by SpendCategory to get group-level counts. Power BI GROUPBY() in the second stage is essential because SpendCategory is a virtual column with no lineage.
Example 6: FILTER + GROUPBY for Time-Based Segmentation in Power BI
Business scenario: Your finance team wants to see, for each region, how many months in 2025 had revenue above $30,000 — essentially counting “good months” per region.
Good Month Count by Region =
VAR MonthlyRegional =
GROUPBY(
FILTER(
'Sales Data',
YEAR('Sales Data'[OrderDate]) = 2025
),
'Sales Data'[Region],
'Sales Data'[Month],
"MonthlyRevenue",
SUMX(CURRENTGROUP(), 'Sales Data'[Revenue])
)
VAR GoodMonths =
FILTER(
MonthlyRegional,
[MonthlyRevenue] > 600
)
RETURN
GROUPBY(
GoodMonths,
'Sales Data'[Region],
"GoodMonthCount",
COUNTX(CURRENTGROUP(), 'Sales Data'[Month])
)

This uses GROUPBY() twice — first to summarize at the Region + Month level, then again after filtering to count qualifying months per region. This double-grouping pattern is exactly what GROUPBY() handles better than SUMMARIZE() because the second grouping operates on a virtual filtered table.
GROUPBY() vs. SUMMARIZE() vs. SUMMARIZECOLUMNS() — The Full Picture
After all these examples, here is an honest, experience-based comparison of all three:
| GROUPBY() | SUMMARIZE() | SUMMARIZECOLUMNS() | |
|---|---|---|---|
| Context transition | No — uses CURRENTGROUP() | Yes — implicit CALCULATE | Yes — handles correctly |
| Works on virtual tables | Yes — best choice | Unreliable for computed columns | Limited |
| Conditional aggregation per group | Yes — FILTER(CURRENTGROUP(), …) | No — requires separate measures | No |
| Adding measure columns | SUMX/AVERAGEX on CURRENTGROUP() | Use ADDCOLUMNS() wrapper | Directly in function |
| BLANK row handling | Includes blank groups | May include blank groups | Excludes blank-measure rows |
| Use in calculated tables | Yes | Yes | Yes |
| Use inside measures | Yes (with VAR) | Yes (with VAR) | Yes (with VAR) |
| Performance on large tables | Moderate — row-level iteration | Moderate | Best — engine-optimized |
| Best use case | Virtual table grouping, conditional aggregation | Physical table grouping (simple) | Physical table grouping (complex) |
The decision tree I use in practice:
- Physical table, simple grouping, need measures → SUMMARIZECOLUMNS()
- Physical table, need extension columns safely → SUMMARIZE() + ADDCOLUMNS()
- Virtual table or computed column grouping → GROUPBY()
- Need conditional aggregation within groups → GROUPBY() with FILTER(CURRENTGROUP(), …)
Important Restrictions of GROUPBY() You Must Know
GROUPBY() has some constraints that aren’t immediately obvious and can cause confusing errors:
1. You cannot use CALCULATE() inside GROUPBY() expressions
CALCULATE() performs a context transition which conflicts with GROUPBY()’s no-transition design. If you try, you’ll get an error:
-- This will ERROR
GROUPBY(
Sales,
Sales[Region],
"Revenue", CALCULATE(SUM(Sales[Revenue])) -- Not allowed
)
-- Use SUMX with CURRENTGROUP() instead
GROUPBY(
Sales,
Sales[Region],
"Revenue", SUMX(CURRENTGROUP(), Sales[Revenue]) -- Correct
)
2. CURRENTGROUP() only works inside GROUPBY()
You cannot use CURRENTGROUP() inside SUMMARIZE(), ADDCOLUMNS(), or any other function. It’s exclusively a GROUPBY() companion.
3. Grouping columns must come from the source table or its related tables
GROUPBY() can group by columns that exist in the source table or columns from tables related to it in the “to-one” direction. It cannot group by arbitrary computed expressions — you need to add those as columns first using ADDCOLUMNS() before passing the table to GROUPBY().
4. GROUPBY() doesn’t automatically remove rows with all-BLANK measures
Unlike SUMMARIZECOLUMNS(), GROUPBY() may include rows where all computed values are BLANK. Add an explicit FILTER() to remove them if needed:
VAR Result =
GROUPBY(Sales, Sales[Region], "Revenue", SUMX(CURRENTGROUP(), Sales[Revenue]))
VAR CleanResult =
FILTER(Result, NOT ISBLANK([Revenue]))
RETURN
CleanResult
Common Mistakes Developers Make with GROUPBY()
Trying to use CALCULATE() inside expression columns
This is the most frequent error. Remember: GROUPBY() is a no-context-transition zone. Swap every CALCULATE() for an iterator (SUMX, AVERAGEX, etc.) over CURRENTGROUP().
Using GROUPBY() when SUMMARIZECOLUMNS() would be faster
On large physical tables with straightforward grouping, SUMMARIZECOLUMNS() is generally better optimized by the DAX engine. Don’t default to GROUPBY() for everything — use it specifically where its unique capabilities (virtual tables, CURRENTGROUP()) are needed.
Forgetting to use CURRENTGROUP() and trying to reference the source table directly
— Wrong: directly referencing Sales inside GROUPBY expression
GROUPBY(Sales, Sales[Region], “Revenue”, SUMX(Sales, Sales[Revenue]))
— This calculates total revenue for ALL rows, not just the current group
— Correct: always use CURRENTGROUP()
GROUPBY(Sales, Sales[Region], “Revenue”, SUMX(CURRENTGROUP(), Sales[Revenue]))
Nesting GROUPBY() without intermediate VARs
Deeply nested GROUPBY() calls are notoriously hard to read and debug. Always break them into named VARs.
Using GROUPBY() on very large tables without pre-filtering
GROUPBY() iterates rows using CURRENTGROUP(). On a 50-million-row table, this can be slow. Always FILTER() the source table down to the relevant subset before passing it to GROUPBY() when possible.
Performance Best Practices
- Pre-filter before GROUPBY(), not after, wherever possible. Filtering the source table before grouping is almost always faster than filtering the grouped result — you’re reducing work, not just hiding results.
- Use Performance Analyzer to measure GROUPBY() measure execution time. Because GROUPBY() uses row-level iteration, it behaves differently from column-optimized functions and can surprise you on large datasets.
- Prefer SUMMARIZECOLUMNS() for physical tables. Reserve GROUPBY() for the specific scenarios where it’s genuinely needed — virtual tables, no-lineage computed columns, and conditional aggregation with CURRENTGROUP().
- Trim virtual tables before passing them to GROUPBY(). If your input table has many columns you don’t need for grouping or aggregation, use SELECTCOLUMNS() to keep only the relevant ones before passing to GROUPBY(). Less data in memory means faster execution.
Conclusion
GROUPBY() fills a very specific and important gap in the DAX function library. It’s not a general replacement for SUMMARIZE() or SUMMARIZECOLUMNS() — but when you’re working with virtual tables, computed columns with no lineage, or need conditional aggregation within groups using CURRENTGROUP(), it’s the cleanest and most reliable tool available.
The combination of FILTER() + GROUPBY() gives you a two-stage analysis capability — filter and group your source data, then filter the grouped result based on aggregated conditions that only exist at the summary level.
You may also like:
- Add Column Using Power BI Power Query Editor
- Select Multiple Values in Power BI Slicer
- Remove Leading Zeros in Power BI
- How to Change Data Type in Power BI

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.