rollup / cube
Subtotals and grand totals in one pass, and how grouping() tells a total row from a real null.
On this page
Show code in
Every code block on the page follows this.
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
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.
| Call | Grouping 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
| region | city | amount |
|---|---|---|
| North | Delhi | 150 |
| North | Jaipur | 70 |
| South | Chennai | 80 |
| South | Kochi | 30 |
rollup(region, city)
| region | city | sales |
|---|---|---|
| North | Delhi | 150 |
| North | Jaipur | 70 |
| North | null | 220 |
| South | Chennai | 80 |
| South | Kochi | 30 |
| South | null | 110 |
| null | null | 330 |
Rows with a null city are region subtotals; the row with both null is the grand total.
Run the example
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:
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
Wrong column order in rollup
cube over many columns
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 questions1. 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.
Go deeper
Primary sources: DataFrame.rollup · DataFrame.cube · GROUP BY (SQL)