ROLLUP

Learn how to use the PostgreSQL ROLLUP subclause of GROUP BY to generate hierarchical subtotals and grand totals for reports and time-series aggregations.

5 min read · Last updated: March 2026 · Back to overview

Quick Answer

The PostgreSQL ROLLUP subclause of GROUP BY generates subtotals along a column hierarchy plus a grand total. Unlike CUBE, ROLLUP respects column order and only produces n+1 grouping sets for n columns, making it ideal for date drill-down reports.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

The ROLLUP subclause of GROUP BY generates hierarchical subtotals and a grand total in a single query. It follows an implied column hierarchy from left to right, making it the standard choice for generating year/month/day drill-down reports and financial summaries without running multiple separate queries.

ROLLUP syntax

Place ROLLUP inside the GROUP BY clause:

SELECT c1, c2, c3, aggregate(c4)
FROM table_name
GROUP BY ROLLUP (c1, c2, c3);

ROLLUP(c1, c2, c3) generates exactly 4 grouping sets following the hierarchy c1 > c2 > c3:

(c1, c2, c3)   -- detail rows
(c1, c2)       -- subtotal: drop rightmost column
(c1)           -- subtotal: drop next column
()             -- grand total

Compare this to CUBE(c1,c2,c3), which would generate all 8 combinations. ROLLUP only produces n+1 sets for n columns.

Basic ROLLUP example

Using a sales table with brand, segment, and quantity:

SELECT
  brand,
  segment,
  SUM(quantity)
FROM sales
GROUP BY ROLLUP (brand, segment)
ORDER BY brand, segment;

Output:

 brand | segment | sum
-------+---------+-----
 ABC   | Basic   | 200
 ABC   | Premium | 100
 ABC   | NULL    | 300  -- brand subtotal
 XYZ   | Basic   | 300
 XYZ   | Premium | 100
 XYZ   | NULL    | 400  -- brand subtotal
 NULL  | NULL    | 700  -- grand total

The hierarchy here is brand > segment. Reversing the order to ROLLUP(segment, brand) would instead produce segment subtotals.

ROLLUP for time-series reports

A classic use of ROLLUP is generating daily, monthly, and yearly rental counts from date data:

SELECT
  EXTRACT(YEAR  FROM rental_date) AS y,
  EXTRACT(MONTH FROM rental_date) AS m,
  EXTRACT(DAY   FROM rental_date) AS d,
  COUNT(rental_id)
FROM rental
GROUP BY ROLLUP (
  EXTRACT(YEAR  FROM rental_date),
  EXTRACT(MONTH FROM rental_date),
  EXTRACT(DAY   FROM rental_date)
)
ORDER BY y, m, d;

This produces one row per day, one subtotal per month, one subtotal per year, and a final grand total — all in a single pass.

Partial ROLLUP

A partial ROLLUP fixes certain columns and applies ROLLUP only to others:

SELECT
  segment,
  brand,
  SUM(quantity)
FROM sales
GROUP BY segment, ROLLUP (brand)
ORDER BY segment, brand;

Here segment is always present; only brand is rolled up. This produces a subtotal per segment but no grand total across all segments.

Key tips for using ROLLUP

  • Column order matters: ROLLUP drops columns from right to left, so put the highest-level dimension first (e.g., year before month before day).
  • Use GROUPING(col) or COALESCE(col::text, 'Total') to relabel NULL subtotal markers in report output.
  • ROLLUP is more efficient than CUBE when not every cross-dimensional combination is needed — it scans less data and produces fewer result rows.
  • ROLLUP produces n+1 grouping sets for n columns; plan the output size accordingly for large dimension tables.
  • Combine ROLLUP with window functions (e.g., LAG) on the result to compute period-over-period changes in a second CTE step.

Reference: PostgreSQL documentation — GROUPING SETS, CUBE, and ROLLUP.

Continue in Group Data: Back to tutorial overview.

Related in this section: PostgreSQL GROUP BY · HAVING · GROUPING SETS

Frequently Asked Questions

What does ROLLUP do in PostgreSQL?

ROLLUP is a subclause of GROUP BY that generates hierarchical subtotals. For ROLLUP(c1, c2, c3) it produces grouping sets (c1, c2, c3), (c1, c2), (c1), and () — a total of n+1 sets for n columns. Each step removes the rightmost column, following the implied hierarchy c1 > c2 > c3. The empty set () produces a grand total row.

What is the difference between ROLLUP and CUBE?

ROLLUP follows a strict left-to-right hierarchy and generates n+1 grouping sets. CUBE generates every possible combination and produces 2^n grouping sets. Use ROLLUP for hierarchical drill-down (year > month > day); use CUBE when you need every cross-dimensional subtotal. ROLLUP is typically used for financial and time-series reports where the hierarchy is meaningful.

How do I use ROLLUP for date-based subtotals in PostgreSQL?

Extract the date parts and pass them to ROLLUP in hierarchy order. For example: GROUP BY ROLLUP(EXTRACT(YEAR FROM date_col), EXTRACT(MONTH FROM date_col), EXTRACT(DAY FROM date_col)). This produces daily subtotals, monthly subtotals, yearly subtotals, and a grand total in a single query.

What is a partial ROLLUP?

A partial ROLLUP keeps some columns fixed in every grouping and applies ROLLUP only to the remaining columns. For example, GROUP BY c1, ROLLUP(c2, c3) always includes c1 in the grouping and generates ROLLUP combinations for c2 and c3. This reduces the number of subtotal rows compared to ROLLUP(c1, c2, c3).

How do I identify grand total and subtotal rows in a ROLLUP result?

Use the GROUPING(col) function, which returns 1 when the column is aggregated across (subtotal or grand total) and 0 for regular grouped rows. You can wrap it in a CASE expression to label rows, or use GROUPING(c1) + GROUPING(c2) to compute a bitmask that distinguishes row types.