ROLLUP, CUBE and GROUPING SETS
A normal GROUP BY gives one level of groups. ROLLUP, CUBE and GROUPING SETS add more
levels to the same result, such as subtotals and a grand total.
Syntax
Order of execution: FROM → WHERE → GROUP BY (every grouping level) → HAVING → SELECT → ORDER BY
CodeSELECT a, b, AGG(column) FROM table_name GROUP BY ROLLUP (a, b); -- or CUBE (a, b), or GROUPING SETS ((a, b), (a), ())
| Clause | Groups it makes |
|---|---|
ROLLUP (a, b) | (a, b), then (a), then () for the grand total |
CUBE (a, b) | every combination: (a, b), (a), (b), () |
GROUPING SETS ((a), (b)) | exactly the groups listed |
GROUPING(a) | 1 on rows where a was added up (a total row), 0 otherwise |
On a total row, the columns being added up show NULL.
PostgreSQL, SQL Server and Oracle have all three. MySQL has only ROLLUP, written
GROUP BY a, b WITH ROLLUP.
Examples
A grand total with ROLLUP
The extra row, with NULL as the method, is the total of all methods.
CodeSELECT method, SUM(amount) AS total FROM payments GROUP BY ROLLUP (method) ORDER BY method;
Subtotals with ROLLUP
With two columns, ROLLUP adds a subtotal for each value of the first, then a grand total.
CodeSELECT category_id, is_available, COUNT(*) AS products FROM products GROUP BY ROLLUP (category_id, is_available) ORDER BY category_id, is_available;
Every combination with CUBE
CUBE also totals the second column on its own: each status across all years.
CodeSELECT EXTRACT(YEAR FROM order_date) AS year, status, COUNT(*) AS orders FROM orders GROUP BY CUBE (EXTRACT(YEAR FROM order_date), status) ORDER BY year, status;
Exactly the groups you list
GROUPING SETS makes only the groups named, plus () for a grand total.
CodeSELECT category_id, is_available, COUNT(*) AS products FROM products GROUP BY GROUPING SETS ((category_id), (is_available), ()) ORDER BY category_id, is_available;
Labelling the total rows
GROUPING() tells a total row apart from a real NULL, and it can sort totals last.
CodeSELECT CASE WHEN GROUPING(method) = 1 THEN 'all methods' ELSE method::text END AS method, COUNT(*) AS payments FROM payments GROUP BY ROLLUP (method) ORDER BY GROUPING(method), payments.method; -- the column, not the text alias -- Fails: COALESCE keeps the column's type, and 'all methods' isn't a payment method SELECT COALESCE(method, 'all methods') FROM payments GROUP BY ROLLUP (method);
Remember
ROLLUPadds totals from right to left;CUBEadds every combination.- A NULL in a total row means "all values"; use
GROUPING()when the column can also hold real NULLs. - Cast enums and numbers to text before putting a label like
'TOTAL'in the same column. ORDER BY GROUPING(column)puts the total rows after the others.
Practice
- Revenue by Status With a Total (Medium)
- The Payments Grid (Medium)
- Orders by City and by Status (Medium)