So far we’ve seen how GROUP BY can summarize data into meaningful results. But what if you need totals and subtotals in the same query—or even want to control exactly which combinations of groupings are returned? That’s where additional GROUP BY options come in: ROLLUP, CUBE, GROUPING SETS, and the GROUPING() function.
Let’s continue using the Gallery.Paintings example table from the previous post to demonstrate how these options can streamline reporting and analysis requests.
Creating Hierarchical Summaries with ROLLUP
Basic ROLLUP Syntax
SELECT column1,
column2,
AGGREGATE_FUNCTION(column3)
FROM table_name
GROUP BY ROLLUP (column1, column2);ROLLUP creates subtotals moving from right to left through the grouping list, plus a grand total.
Example: Paintings by Country and Artist, with Totals
SELECT CountryOfOrigin,
Artist,
COUNT(1) AS PaintingCount
FROM Gallery.Paintings
WHERE CountryOfOrigin IN ('Italy','Netherlands','Spain')
GROUP BY ROLLUP (CountryOfOrigin, Artist)
ORDER BY CountryOfOrigin,
Artist;Result:
| CountryOfOrigin | Artist | PaintingCount |
|---|---|---|
| Italy | Leonardo da Vinci | 2 |
| Italy | NULL | 2 |
| Netherlands | Johannes Vermeer | 3 |
| Netherlands | Vincent van Gogh | 3 |
| Netherlands | NULL | 6 |
| Spain | Salvador Dalí | 3 |
| Spain | NULL | 3 |
| NULL | NULL | 11 |
Exploring All Combinations with CUBE
Basic CUBE Syntax
SELECT column1,
column2,
AGGREGATE_FUNCTION(column3)
FROM table_name
GROUP BY CUBE (column1, column2);CUBE generates subtotals for all possible combinations of the grouping columns, plus a grand total.
Example: Paintings by Country and Artist, with All Combinations
SELECT CountryOfOrigin,
Artist,
COUNT(1) AS PaintingCount
FROM Gallery.Paintings
WHERE CountryOfOrigin IN ('Italy','Netherlands','Spain')
GROUP BY CUBE (CountryOfOrigin, Artist)
ORDER BY CountryOfOrigin,
Artist;Result:
| CountryOfOrigin | Artist | PaintingCount |
|---|---|---|
| Italy | Leonardo da Vinci | 2 |
| Italy | NULL | 2 |
| Netherlands | Johannes Vermeer | 3 |
| Netherlands | Vincent van Gogh | 3 |
| Netherlands | NULL | 6 |
| Spain | Salvador Dalí | 3 |
| Spain | NULL | 3 |
| NULL | Leonardo da Vinci | 2 |
| NULL | Johannes Vermeer | 3 |
| NULL | Vincent van Gogh | 3 |
| NULL | Salvador Dalí | 3 |
| NULL | NULL | 11 |
Customizing Subtotals with GROUPING SETS
Basic GROUPING SETS Syntax
SELECT column1,
column2,
AGGREGATE_FUNCTION(column3)
FROM table_name
GROUP BY GROUPING SETS (
(column1, column2), -- detail
(column1), -- subtotal by column1
(column2), -- subtotal by column2
() -- grand total
);GROUPING SETS lets you explicitly define which detail levels, subtotals, and totals to include.
Example: Paintings by Country and Century, with Custom Subtotals
SELECT CountryOfOrigin,
(YearCreated / 100) * 100 AS Century,
COUNT(1) AS PaintingCount
FROM Gallery.Paintings
WHERE CountryOfOrigin IN ('Italy','Netherlands','Spain')
GROUP BY GROUPING SETS (
(CountryOfOrigin, (YearCreated / 100) * 100), -- detail
(CountryOfOrigin), -- subtotal by country
((YearCreated / 100) * 100), -- subtotal by century
() -- grand total
)
ORDER BY CountryOfOrigin,
Century;Result:
| CountryOfOrigin | Century | PaintingCount |
|---|---|---|
| Italy | 1400 | 1 |
| Italy | 1500 | 1 |
| Italy | NULL | 2 |
| Netherlands | 1600 | 3 |
| Netherlands | 1800 | 3 |
| Netherlands | NULL | 6 |
| Spain | 1900 | 3 |
| Spain | NULL | 3 |
| NULL | 1400 | 1 |
| NULL | 1500 | 1 |
| NULL | 1600 | 3 |
| NULL | 1800 | 3 |
| NULL | 1900 | 3 |
| NULL | NULL | 11 |
Distinguishing Totals with GROUPING()
Basic GROUPING() Syntax
SELECT column1,
column2,
AGGREGATE_FUNCTION(column3),
GROUPING(column1) AS IsCol1Subtotal,
GROUPING(column2) AS IsCol2Subtotal
FROM table_name
GROUP BY ROLLUP (column1, column2);GROUPING() helps you distinguish between:
- Real
NULLvalues in your data NULLs introduced byROLLUP,CUBE, orGROUPING SETSas subtotal markers
Table: Gallery.Statues
| StatueID | Title | Artist | YearCreated | CountryOfOrigin |
|---|---|---|---|---|
| 1 | Venus di Milo | NULL | -125 | Greece |
| 2 | David | Michelangelo di Lodovico Buonarroti Simoni | 1504 | Italy |
| 3 | The Thinker | Auguste Rodin | 1904 | France |
Example: Using GROUPING() with Statues by Country and Artist
SELECT CountryOfOrigin,
Artist,
COUNT(1) AS StatueCount,
GROUPING(CountryOfOrigin) AS IsCountrySubtotal,
GROUPING(Artist) AS IsArtistSubtotal
FROM Gallery.Statues
GROUP BY ROLLUP (CountryOfOrigin, Artist)
ORDER BY CountryOfOrigin,
Artist;Result:
| CountryOfOrigin | Artist | StatueCount | IsCountrySubtotal | IsArtistSubtotal |
|---|---|---|---|---|
| France | Auguste Rodin | 1 | 0 | 0 |
| France | NULL (subtotal) | 1 | 0 | 1 |
| Greece | NULL | 1 | 0 | 0 |
| Greece | NULL (subtotal) | 1 | 0 | 1 |
| Italy | Michelangelo di Lodovico Buonarroti Simoni | 1 | 0 | 0 |
| Italy | NULL (subtotal) | 1 | 0 | 1 |
| NULL | NULL | 3 | 1 | 1 |
Which Option to Choose
- ROLLUP → Best for hierarchies (e.g., Country → Artist → Grand Total). Use it when you need running subtotals and a grand total.
- CUBE → Best for multi-dimensional analysis where you want all possible combinations of grouping columns (like pivot table summaries).
- GROUPING SETS → Best for precise control, when you want only specific subtotals and totals without the “extra” combinations.
- GROUPING() → Best for clarity in reporting, to tell the difference between a true
NULLin your data and a subtotal row introduced by grouping logic.
Compatibility and Syntax Differences
- SQL Server → Supports
ROLLUP,CUBE,GROUPING SETS, andGROUPING(). - Oracle → Supports all, but may require
EXTRACT()for date parts. - PostgreSQL → Full support for all features.
- MySQL → Supports
ROLLUP(viaWITH ROLLUP), but notCUBE,GROUPING SETS, orGROUPING(). - SQLite → Does not support these features directly; you can simulate with
UNION ALL.
Key Takeaways
ROLLUPadds hierarchical subtotals and a grand total.CUBEgenerates all possible subtotal and total combinations.GROUPING SETSlets you define exactly which subtotals to include.GROUPING()distinguishes between actual NULL values and those added by grouping.- Feature support varies across platforms—SQL Server, Oracle, and PostgreSQL have full support, while MySQL and SQLite are more limited.
As you’ve seen, advanced grouping options often produce subtotal and total rows with NULL placeholders. In the next post, we’ll look at CASE expressions which can be combined with GROUPING() to replace those NULLs with clear, meaningful labels.

