ROLLUP adds subtotal rows for each level plus a grand total, without a separate UNION.
-- Sales by region and category, with per-region subtotals and a grand total
SELECT region, category, SUM(amount) AS total
FROM sales
GROUP BY ROLLUP (region, category)
ORDER BY region, category;
The extra rows have NULL in the rolled-up columns. Label them with GROUPING():
SELECT
CASE WHEN GROUPING(region) = 1 THEN 'All regions' ELSE region END AS region,
CASE WHEN GROUPING(category) = 1 THEN 'All categories' ELSE category END AS category,
SUM(amount) AS total
FROM sales
GROUP BY ROLLUP (region, category);
Dialect notes:
- PostgreSQL, BigQuery, Snowflake, DuckDB:
GROUP BY ROLLUP (a, b)(andGROUPING SETS,CUBE). - MySQL: different syntax —
GROUP BY a, b WITH ROLLUP(no parentheses;GROUPING()works in 8.0+). - SQLite: no
ROLLUP. Emulate withUNION ALL:
SELECT region, category, SUM(amount) FROM sales GROUP BY region, category
UNION ALL SELECT region, NULL, SUM(amount) FROM sales GROUP BY region
UNION ALL SELECT NULL, NULL, SUM(amount) FROM sales;