Power BI DAX GROUPBY With FILTER: Complete Practical Guide

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>
)
How To Use The GROUPBY Function In Power BI

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.

  1. Open Power BI Desktop
  2. Click Get Data
  3. Choose Excel
  4. Select the sample Excel file Load:
    • Sales Data
    • Customers table
What Is GROUPBY() in Power BI DAX

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])
)
How to write DAX to filter dataset and then apply Group by

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])
)
FILTER Before GROUPBY in Power BI Desktop

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
)
Power BI Group By Using DAX & Power Query

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)
Power BI GROUPBY Function

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)
How can I calculate a group by aggregate, while filtering in Power BI

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
)
)
)
DAX SUMMARIZE() Grouping and Summarizing Data

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]
)
Multi-Column Grouping with Downstream FILTER() in Power BI

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
)
)
)
Customers grouped as Above Average and Below Average based on total sales in Power BI

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])
)
Count of months where each region generated revenue in Power BI

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 transitionNo — uses CURRENTGROUP()Yes — implicit CALCULATEYes — handles correctly
Works on virtual tablesYes — best choiceUnreliable for computed columnsLimited
Conditional aggregation per groupYes — FILTER(CURRENTGROUP(), …)No — requires separate measuresNo
Adding measure columnsSUMX/AVERAGEX on CURRENTGROUP()Use ADDCOLUMNS() wrapperDirectly in function
BLANK row handlingIncludes blank groupsMay include blank groupsExcludes blank-measure rows
Use in calculated tablesYesYesYes
Use inside measuresYes (with VAR)Yes (with VAR)Yes (with VAR)
Performance on large tablesModerate — row-level iterationModerateBest — engine-optimized
Best use caseVirtual table grouping, conditional aggregationPhysical 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:

Power Apps functions free pdf

30 Power Apps Functions

This free guide walks you through the 30 most-used Power Apps functions with real business examples, exact syntax, and results you can see.

Download User registration canvas app

DOWNLOAD USER REGISTRATION POWER APPS CANVAS APP

Download a fully functional Power Apps Canvas App (with Power Automate): User Registration App