You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/t-sql/queries/select-group-by-transact-sql.md
+72-49Lines changed: 72 additions & 49 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,10 +1,10 @@
1
1
---
2
-
title: "GROUP BY (Transact-SQL)"
2
+
title: GROUP BY (Transact-SQL)
3
3
description: A SELECT statement clause that divides the query result into groups of rows, usually by performing one or more aggregations on each group.
4
4
author: MikeRayMSFT
5
5
ms.author: mikeray
6
6
ms.reviewer: randolphwest
7
-
ms.date: 02/02/2026
7
+
ms.date: 03/13/2026
8
8
ms.service: sql
9
9
ms.subservice: t-sql
10
10
ms.topic: reference
@@ -30,25 +30,23 @@ helpviewer_keywords:
30
30
- "groups [SQL Server], tables divided into groups"
A `SELECT` statement clause that divides the query result into groups of rows, usually by performing one or more aggregations on each group. The `SELECT` statement returns one row per group.
41
+
A `SELECT` statement clause that divides the query result into groups of rows, usually by performing one or more aggregations on each group. The `SELECT` statement returns one row for each group.
Syntax for Analytics Platform System/Paralel Data Warehouse (APS/PDW):
92
+
Syntax for Analytics Platform System (PDW):
94
93
95
94
```syntaxsql
96
95
GROUP BY {
@@ -107,32 +106,45 @@ Specifies a column or a nonaggregate calculation on a column. This column can be
107
106
108
107
For valid expressions, see [expression](../language-elements/expressions-transact-sql.md).
109
108
110
-
The column must appear in the `FROM` clause of the `SELECT` statement, but isn't required to appear in the `SELECT` list. However, each table or view column in any nonaggregate expression in the `<select>` list must be included in the `GROUP BY` list.
109
+
The column must appear in the `FROM` clause of the `SELECT` statement, but isn't required to appear in the `SELECT` list. However, you must include each table or view column in the `GROUP BY` list if you use it in any nonaggregate expression in the `<select>` list.
111
110
112
111
### GROUP BY options
113
112
114
-
The following options extend the basic `GROUP BY` clause to support hierarchical aggregation, multidimensional summarization, custom grouping combinations, and platform‑specific execution behaviors. These options allow queries to produce subtotals and grand totals in a single logical operation.
115
-
116
-
-**ROLLUP ( <group_by_expression> [ , ...n ] )**
117
-
Generates hierarchical subtotals for the listed columns and a final grand total (for example, `(a,b,c)`, `(a,b)`, `(a)`, `()`). Use for drill‑up reports like **year** > **quarter** > **month**.
118
-
-**CUBE ( <group_by_expression> [ , ...n ] )**
119
-
Produces all combinations of the specified columns (the full 2^n lattice) plus the grand total. Best suited for multi‑dimensional analysis across every slice.
120
-
-**GROUPING SETS ( <grouping_set> [ , ...n ] )**
121
-
Defines the exact groupings to compute (including `()` for grand total) in one pass; functionally similar to a `UNION ALL` of multiple `GROUP BY` queries but optimized together.
122
-
-**`()` (empty grouping set)**
123
-
Shorthand for computing only the **grand total** across all rows—used alone as `GROUP BY ()` or inside `GROUPING SETS`.
The following options extend the basic `GROUP BY` clause to support hierarchical aggregation, multidimensional summarization, custom grouping combinations, and platform-specific execution behaviors. Queries can use these options to produce subtotals and grand totals in a single logical operation.
114
+
115
+
-**ROLLUP ( \<group_by_expression> [ , ...n ] )**
116
+
117
+
Generates hierarchical subtotals for the listed columns and a final grand total (for example, `(a,b,c)`, `(a,b)`, `(a)`, `()`). Use it for drill-up reports like **year** > **quarter** > **month**.
118
+
119
+
-**CUBE ( \<group_by_expression> [ , ...n ] )**
120
+
121
+
Produces all combinations of the specified columns (the full 2^n lattice) plus the grand total. Use it for multi-dimensional analysis across every slice.
122
+
123
+
-**GROUPING SETS ( \<grouping_set> [ , ...n ] )**
124
+
125
+
Defines the exact groupings to compute (including `()` for grand total) in one pass. This option is functionally similar to a `UNION ALL` of multiple `GROUP BY` queries but optimized together.
126
+
127
+
-**() (empty grouping set)**
128
+
129
+
Shorthand for computing only the **grand total** across all rows. Use it alone as `GROUP BY ()` or inside `GROUPING SETS`.
Older, non‑ISO syntax equivalent to `GROUP BY CUBE(...)` or `GROUP BY ROLLUP(...)`. Supported for backward compatibility; use the ISO subclauses when possible.
Older, non-ISO syntax that's equivalent to `GROUP BY CUBE(...)` or `GROUP BY ROLLUP(...)`. Supported for backward compatibility only. Use the ISO subclauses when possible.
138
+
128
139
-**WITH (DISTRIBUTED_AGG)**
129
-
Hints distributed execution for aggregations when grouping by a single column. It's supported only in Azure Synapse Analytics dedicated SQL pools and Analytics Platform System/Parallel Data Warehouse (APS/PDW).
130
140
131
-
## GROUP BY *column-expression*[ ,...n ]
141
+
Hints distributed execution for aggregations when grouping by a single column. Azure Synapse Analytics dedicated SQL pools and Analytics Platform System (PDW) are the only platforms that support this option.
142
+
143
+
### GROUP BY *column-expression*[ ,...n ]
132
144
133
145
Groups the `SELECT` statement results according to the values in a list of one or more column expressions.
134
146
135
-
For example, this query creates a `Sales` table with columns for `Region`, `Territory`, and `Sales`. It inserts four rows and two of the rows have matching values for `Region` and `Territory`.
147
+
For example, this query creates a `Sales` table with columns for `Region`, `Territory`, and `Sales`. It inserts four rows, and two of the rows have matching values for `Region` and `Territory`.
136
148
137
149
```sql
138
150
CREATETABLESales
@@ -218,9 +230,9 @@ FROM T
218
230
GROUP BY ColumnA + ColumnB;
219
231
```
220
232
221
-
## GROUP BY ROLLUP ()
233
+
###GROUP BY ROLLUP ()
222
234
223
-
Creates a group for each combination of column expressions. In addition, it *rolls up* the results into subtotals and grand totals. While creating the groups, it moves from right to left, decreasing the number of column expressions over which it creates groups and the aggregations.
235
+
Creates a group for each combination of column expressions. In addition, it *rolls up* the results into subtotals and grand totals. When it creates the groups, it moves from right to left, decreasing the number of column expressions for grouping and aggregations.
224
236
225
237
The column order affects the `ROLLUP` output and can affect the number of rows in the result set.
226
238
@@ -230,9 +242,9 @@ For example, `GROUP BY ROLLUP (col1, col2, col3, col4)` creates groups for each
230
242
- col1, col2, col3, NULL
231
243
- col1, col2, NULL, NULL
232
244
- col1, NULL, NULL, NULL
233
-
- NULL, NULL, NULL, NULL (The group with the NULL values is the grand total)
245
+
- NULL, NULL, NULL, NULL (The group with the `NULL` values is the grand total)
234
246
235
-
Using the table from the previous example, this code runs a `GROUP BY ROLLUP` operation instead of a simple`GROUP BY`.
247
+
Using the table from the previous example, this code runs a `GROUP BY ROLLUP` operation instead of a basic`GROUP BY`.
236
248
237
249
```sql
238
250
SELECT Region,
@@ -242,7 +254,7 @@ FROM Sales
242
254
GROUP BY ROLLUP(Region, Territory);
243
255
```
244
256
245
-
The query result has the same aggregations as the simple`GROUP BY` without the `ROLLUP`. In addition, it creates subtotals for each value of Region. Finally, it gives a grand total for all rows. The result looks like this:
257
+
The query result has the same aggregations as the basic`GROUP BY` without the `ROLLUP`. In addition, it creates subtotals for each value of Region. Finally, it gives a grand total for all rows. The result looks like this:
246
258
247
259
| Region | Territory | TotalSales |
248
260
| --- | --- | ---: |
@@ -253,7 +265,7 @@ The query result has the same aggregations as the simple `GROUP BY` without the
253
265
| United States | NULL | 100 |
254
266
| NULL | NULL | 700 |
255
267
256
-
## GROUP BY CUBE ()
268
+
###GROUP BY CUBE ()
257
269
258
270
`GROUP BY CUBE` creates groups for all possible combinations of columns. For `GROUP BY CUBE (a, b)`, the results have groups for unique values of `(a, b)`, `(NULL, b)`, `(a, NULL)`, and `(NULL, NULL)`.
259
271
@@ -281,7 +293,7 @@ The query result has groups for unique values of `(Region, Territory)`, `(NULL,
281
293
| Canada | NULL | 600 |
282
294
| United States | NULL | 100 |
283
295
284
-
## GROUP BY GROUPING SETS ()
296
+
###GROUP BY GROUPING SETS ()
285
297
286
298
The `GROUPING SETS` option combines multiple `GROUP BY` clauses into one `GROUP BY` clause. The results are the same as using `UNION ALL` on the specified groups.
287
299
@@ -315,7 +327,7 @@ GROUP BY CUBE(Region, Territory);
315
327
316
328
SQL doesn't consolidate duplicate groups generated for a `GROUPING SETS` list. For example, in `GROUP BY ((), CUBE (Region, Territory))`, both elements return a row for the grand total, and both rows appear in the results.
317
329
318
-
### Support for ISO and ANSI SQL-2006 GROUP BY features
330
+
####Support for ISO and ANSI SQL-2006 GROUP BY features
319
331
320
332
The `GROUP BY` clause supports all `GROUP BY` features that are included in the SQL-2006 standard with the following syntax exceptions:
321
333
@@ -334,7 +346,7 @@ FROM Sales
334
346
GROUP BY GROUPING SETS(Region, ());
335
347
```
336
348
337
-
## GROUP BY ALL column-expression [ ,...n ]
349
+
###GROUP BY ALL column-expression [ ,...n ]
338
350
339
351
**Applies to**: SQL Server and Azure SQL Database
340
352
@@ -348,23 +360,24 @@ Specifies whether to include all groups in the results, regardless of whether th
348
360
- Isn't supported in queries that access remote tables if there's also a `WHERE` clause in the query.
349
361
- Fails on columns that have the FILESTREAM attribute.
350
362
351
-
### Support for ISO and ANSI SQL-2006 GROUP BY Features
363
+
####Support for ISO and ANSI SQL-2006 GROUP BY features
352
364
353
365
The `GROUP BY` clause supports all `GROUP BY` features that are included in the SQL-2006 standard with the following syntax exceptions:
354
-
-`GROUP BY ALL` and `GROUP BY DISTINCT` are only allowed in a simple `GROUP BY` clause that contains column expressions. You can't use them with the `GROUPING SETS`, `ROLLUP`, `CUBE`, `WITH CUBE`, or `WITH ROLLUP` constructs. `ALL` is the default and is implicit. It's also only allowed in the backward compatible syntax.
355
366
356
-
## GROUP BY column-expression [ ,...n ] WITH { CUBE | ROLLUP }
367
+
- You can only use `GROUP BY ALL` and `GROUP BY DISTINCT` in a basic `GROUP BY` clause that contains column expressions. You can't use them with the `GROUPING SETS`, `ROLLUP`, `CUBE`, `WITH CUBE`, or `WITH ROLLUP` constructs. `ALL` is the default and is implicit. You can only use it in the backward compatible syntax.
368
+
369
+
### GROUP BY column-expression [ ,...n ] WITH { CUBE | ROLLUP }
357
370
358
371
**Applies to**: SQL Server and Azure SQL Database
359
372
360
373
> [!NOTE]
361
374
> Use this syntax only for backward compatibility. Avoid using this syntax in new development work, and plan to modify applications that currently use this syntax.
362
375
363
-
## WITH (DISTRIBUTED_AGG)
376
+
###WITH (DISTRIBUTED_AGG)
364
377
365
378
**Applies to**: [!INCLUDE [ssazuresynapse-md](../../includes/ssazuresynapse-md.md)] and [!INCLUDE [ssPDW](../../includes/sspdw-md.md)]
366
379
367
-
The `DISTRIBUTED_AGG` query hint forces the massively parallel processing (MPP) system to redistribute a table on a specific column before performing an aggregation. Only one column in the `GROUP BY` clause can have a `DISTRIBUTED_AGG` query hint. After the query finishes, the redistributed table is dropped. The original table isn't changed.
380
+
The `DISTRIBUTED_AGG` query hint forces the massively parallel processing (MPP) system to redistribute a table on a specific column before performing an aggregation. You can use the `DISTRIBUTED_AGG` query hint on only one column in the `GROUP BY` clause. After the query finishes, the redistributed table is dropped. The original table isn't changed.
368
381
369
382
> [!NOTE]
370
383
> The `DISTRIBUTED_AGG` query hint provides backward compatibility with earlier [!INCLUDE [ssPDW](../../includes/sspdw-md.md)] versions and doesn't improve performance for most queries. By default, MPP already redistributes data as necessary to improve performance for aggregations.
@@ -392,9 +405,9 @@ The `DISTRIBUTED_AGG` query hint forces the massively parallel processing (MPP)
392
405
393
406
`NULL` values:
394
407
395
-
- If a grouping column contains `NULL` values, all `NULL` values are considered equal, and they're collected into a single group.
408
+
- If a grouping column contains `NULL` values, the Database Engine treats all `NULL` values as equal and collects them into a single group.
396
409
397
-
###Limitations
410
+
## Limitations
398
411
399
412
**Applies to**: SQL Server and [!INCLUDE [ssazuresynapse-md](../../includes/ssazuresynapse-md.md)]
400
413
@@ -421,14 +434,14 @@ For a `GROUP BY` clause that uses `ROLLUP`, `CUBE`, or `GROUPING SETS`, the maxi
421
434
422
435
For backward compatible `GROUP BY` clauses that don't contain `CUBE` or `ROLLUP`, the `GROUP BY` column sizes, the aggregated columns, and the aggregate values involved in the query limit the number of `GROUP BY` items. This limit originates from the limit of 8,060 bytes on the intermediate worktable that holds intermediate query results. You can use a maximum of 12 grouping expressions when you specify `CUBE` or `ROLLUP`.
423
436
424
-
###Comparison of supported `GROUP BY` features
437
+
## Comparison of supported GROUP BY features
425
438
426
-
The following table describes the `GROUP BY` features that different SQL Server versions and database compatibility levels support.
439
+
The following table describes the `GROUP BY` features that different products support.
427
440
428
-
| Feature | SQL Server Integration Services | SQL Server compatibility level 100 or higher|
441
+
| Feature | SQL Server Integration Services | SQL Server <sup>1</sup>|
429
442
| --- | --- | --- |
430
443
|`DISTINCT` aggregates | Not supported for `WITH CUBE` or `WITH ROLLUP`. | Supported for `WITH CUBE`, `WITH ROLLUP`, `GROUPING SETS`, `CUBE`, or `ROLLUP`. |
431
-
| User-defined function with `CUBE` or `ROLLUP` name in the `GROUP BY` clause | User-defined function `dbo.cube(<arg1>, ...<argN>)` or `dbo.rollup(<arg1>, ...<argN>)` in the `GROUP BY` clause is allowed.<br /><br />For example: `SELECT SUM (x) FROM T GROUP BY dbo.cube(y);`| User-defined function `dbo.cube (<arg1>, ...<argN>)` or `dbo.rollup(arg1>, ...<argN>)` in the `GROUP BY` clause isn't allowed.<br /><br />For example: `SELECT SUM (x) FROM T GROUP BY dbo.cube(y);`<br /><br />SQL Server returns the following error message: "Incorrect syntax near the keyword 'cube'|'rollup'."<br /><br />To avoid this problem, replace `dbo.cube` with `[dbo].[cube]` or `dbo.rollup` with `[dbo].[rollup]`.<br /><br />The following example is allowed: `SELECT SUM (x) FROM T GROUP BY [dbo].[cube](y);`|
444
+
| User-defined function with `CUBE` or `ROLLUP` name in the `GROUP BY` clause | User-defined function `dbo.cube(<arg1>, ...<argN>)` or `dbo.rollup(<arg1>, ...<argN>)` in the `GROUP BY` clause is allowed.<br /><br />For example: `SELECT SUM (x) FROM T GROUP BY dbo.cube(y);`| User-defined function `dbo.cube (<arg1>, ...<argN>)` or `dbo.rollup(<arg1>, ...<argN>)` in the `GROUP BY` clause isn't allowed.<br /><br />For example: `SELECT SUM (x) FROM T GROUP BY dbo.cube(y);`<br /><br />SQL Server returns an error message <sup>2</sup>.<br /><br />To avoid this problem, replace `dbo.cube` with `[dbo].[cube]` or `dbo.rollup` with `[dbo].[rollup]`.<br /><br />The following example is allowed: `SELECT SUM (x) FROM T GROUP BY [dbo].[cube](y);`|
432
445
|`GROUPING SETS`| Not supported | Supported |
433
446
|`CUBE`| Not supported | Supported |
434
447
|`ROLLUP`| Not supported | Supported |
@@ -439,8 +452,14 @@ The following table describes the `GROUP BY` features that different SQL Server
The following example retrieves the total for each `SalesOrderID` from the `SalesOrderDetail` table. This example uses AdventureWorks.
@@ -492,7 +511,7 @@ HAVING DATEPART(yyyy, OrderDate) >= N'2003'
492
511
ORDER BY DATEPART(yyyy, OrderDate);
493
512
```
494
513
495
-
## Examples: Azure Synapse Analytics and Analytics Platform System / Parallel Data Warehouse (PDW)
514
+
## Examples: Azure Synapse Analytics and Analytics Platform System (PDW)
496
515
497
516
### E. Basic use of the GROUP BY clause
498
517
@@ -530,16 +549,20 @@ SELECT LastName,
530
549
FirstName
531
550
FROM DimCustomer
532
551
GROUP BY LastName, FirstName;
552
+
533
553
SELECT NumberCarsOwned
534
554
FROM DimCustomer
535
555
GROUP BY YearlyIncome, NumberCarsOwned;
556
+
536
557
SELECT (SalesAmount + TaxAmt + Freight) AS TotalCost
537
558
FROM FactInternetSales
538
559
GROUP BY SalesAmount, TaxAmt, Freight;
560
+
539
561
SELECT SalesAmount,
540
562
SalesAmount *1.10AS SalesTax
541
563
FROM FactInternetSales
542
564
GROUP BY SalesAmount;
565
+
543
566
SELECT SalesAmount
544
567
FROM FactInternetSales
545
568
GROUP BY SalesAmount, SalesAmount *1.10;
@@ -565,7 +588,7 @@ ORDER BY OrderDateKey;
565
588
566
589
### I. Use a GROUP BY clause with a HAVING clause
567
590
568
-
The following example uses the `HAVING` clause to specify the groups generated in the `GROUP BY` clause that should be included in the result set. Only those groups with order dates in 2004 or later are included in the results.
591
+
The following example uses the `HAVING` clause to specify the groups generated in the `GROUP BY` clause that should be includes in the result set. Only those groups with order dates in 2004 or later are included in the results.
0 commit comments