Skip to content
Great engineers know 3 min read · Reshaping 2 practice problems ↓

rollup / cube

Subtotals and grand totals in one pass, and how grouping() tells a total row from a real null.

You will learn

  • How rollup adds subtotals and a grand total in one pass
  • How cube differs from rollup
  • How grouping() tells a total row from a real null
  • How to write the same with GROUPING SETS in SQL

Read first

Comfortable with these? Read on.

TL;DR rollup("region", "city") aggregates by (region, city), then by (region), then over everything. cube adds every combination. In the extra rows, rolled-up columns are null; grouping(col) returns 1 when that null means "total".

What it does

A report often needs detail rows, subtotals and a grand total. Without rollup you would run three aggregations and union them. rollup produces all of them in one query.

CallGrouping sets produced
groupBy("region", "city")(region, city)
rollup("region", "city")(region, city), (region), ()
cube("region", "city")(region, city), (region), (city), ()

rollup is hierarchical: it removes columns from the right, so the column order matters. cube produces all 2n combinations.

Step by step

sales

regioncityamount
NorthDelhi150
NorthJaipur70
SouthChennai80
SouthKochi30

rollup(region, city)

regioncitysales
NorthDelhi150
NorthJaipur70
Northnull220
SouthChennai80
SouthKochi30
Southnull110
nullnull330

Rows with a null city are region subtotals; the row with both null is the grand total.

Run the example

PySparkSpark SQL
from pyspark.sql import functions as F
result = (sales
    .rollup("region", "city")
    .agg(F.sum("amount").alias("sales"),
         F.grouping("city").alias("is_region_total"),
         F.grouping("region").alias("is_grand_total"))
    .orderBy(F.col("region").asc_nulls_last(), F.col("city").asc_nulls_last()))
SELECT region, city, SUM(amount) AS sales,
       GROUPING(city)   AS is_region_total,
       GROUPING(region) AS is_grand_total
FROM sales
GROUP BY ROLLUP(region, city)
ORDER BY region NULLS LAST, city NULLS LAST

Switch to PySpark to edit and run this example in your browser.

Real nulls vs total rows

If the data itself has a null city, a detail row and a subtotal row both show city = null. grouping("city") is 0 for the detail row and 1 for the subtotal. Use it to label rows: F.when(F.grouping("city") == 1, "All cities").otherwise(F.col("city")). grouping_id() returns all the flags as one bit vector, handy for filtering a specific level.

GROUPING SETS in SQL

rollup and cube are shorthands for GROUPING SETS, which lets you list exactly the combinations you want:

Spark SQL
SELECT region, city, SUM(amount) AS total
FROM sales
GROUP BY GROUPING SETS ((region, city), (region), ())

Older PySpark versions have no grouping-sets method on DataFrames (recent ones add groupingSets), so for arbitrary combinations use SQL or union several groupBys.

Under the hood

Spark implements rollup and cube with an Expand operator: each input row is copied once per grouping set, with the rolled-up columns set to null and a hidden grouping id added, and then a single aggregation runs. For rollup over n columns that means n + 1 copies; cube over n columns means 2n copies. cube over 6 columns copies every row 64 times before the shuffle, so keep cube to a few columns.

Common mistakes

Confusing subtotal rows with real nulls

Use grouping() to tell them apart.

Wrong column order in rollup

rollup(city, region) produces city subtotals and a meaningless (city) level. Put the coarsest column first.

cube over many columns

It multiplies rows by 2n before aggregating.

Key takeaways

  • rollup gives detail rows, hierarchical subtotals and a grand total in one pass.
  • cube gives every combination of the columns.
  • grouping(col) is 1 when a null means "total".
  • Both copy each row once per grouping set before aggregating.

Check yourself

3 questions

1. How many grouping sets does rollup(a, b, c) produce?

Show the answer

4. (a, b, c), (a, b), (a) and (): n + 1 sets.

2. How many does cube(a, b, c) produce?

Show the answer

8. Every subset of three columns: 2³ = 8.

3. A row has city = null and grouping("city") = 0. What is it?

Show the answer

A detail row whose city is really null. grouping returns 0 when the column was part of the grouping, so the null comes from the data.

Practice it

Interview problems that use rollup / cube: write the PySpark, run it, and get graded on hidden tests.

Solve: Subtotals with ROLLUP →

Go deeper

Primary sources: DataFrame.rollup · DataFrame.cube · GROUP BY (SQL)