UNION,ROLLUP, CUBE,GROUPING SETS
2026-04-01
GROUP BYUNION ALLROLLUP and GROUPING SETSUNION is a set operator that combines the results of multiple SELECT statements into a single result set.
SELECT must return the same number of columnsUNIONUNION ALL| Operator | Behavior |
|---|---|
| UNION | Removes duplicate rows |
| UNION ALL | Preserves all rows |
Two independent result sets:
UNION applies deduplication
UNION ALL performs pure concatenation
Table A
| id | value |
|---|---|
| 1 | A |
| 2 | B |
| 3 | C |
Table B
| id | value |
|---|---|
| 3 | C |
| 4 | D |
| 5 | E |
\[\Downarrow\]
| id | value |
|---|---|
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
| 5 | E |
\[\Downarrow\]
| id | value |
|---|---|
| 1 | A |
| 2 | B |
| 3 | C |
| 3 | C |
| 4 | D |
| 5 | E |
We now move from toy examples to a real analytical problem.
We are asked to provide:
All results must appear in one result set.
All queries rely on the following analytical layer.
CREATE MATERIALIZED VIEW analytics.mv_order_revenue AS
SELECT
c.country_name,
r.region_name,
ci.city_name,
p.category,
oi.quantity * p.price AS revenue
FROM analytics.order_items oi
JOIN analytics.orders o
ON oi.order_id = o.order_id
JOIN analytics.products p
ON oi.product_id = p.product_id
JOIN analytics.customers cu
ON o.customer_id = cu.customer_id
JOIN analytics.cities ci
ON cu.city_id = ci.city_id
JOIN analytics.regions r
ON ci.region_id = r.region_id
JOIN analytics.countries c
ON r.country_id = c.country_id;SELECT
country_name,
region_name,
city_name,
SUM(revenue) AS total_revenue
FROM analytics.mv_order_revenue
GROUP BY country_name, region_name, city_name
UNION ALL
SELECT
country_name,
region_name,
NULL,
SUM(revenue)
FROM analytics.mv_order_revenue
GROUP BY country_name, region_name
UNION ALL
SELECT
country_name,
NULL,
NULL,
SUM(revenue)
FROM analytics.mv_order_revenue
GROUP BY country_name
UNION ALL
SELECT
NULL,
NULL,
NULL,
SUM(revenue)
FROM analytics.mv_order_revenue;| country_name | region_name | city_name | total_revenue |
|---|---|---|---|
| Armenia | Lori | Vanadzor | 272.76 |
| Armenia | Shirak | Gyumri | 530.66 |
| Armenia | Yerevan | Yerevan | 1645.10 |
| Georgia | Adjara | Batumi | 525.73 |
| Georgia | Tbilisi | Tbilisi | 461.67 |
| Georgia | Tbilisi | NULL | 461.67 |
| Armenia | Shirak | NULL | 530.66 |
| Armenia | Lori | NULL | 272.76 |
| Armenia | Yerevan | NULL | 1645.10 |
| Georgia | Adjara | NULL | 525.73 |
| Armenia | NULL | NULL | 2448.59 |
| Georgia | NULL | NULL | 987.40 |
| NULL | NULL | NULL | 3435.99 |
Let’s expand the problem with the new New requirement
SELECT
country_name,
region_name,
city_name,
category,
SUM(revenue) AS total_revenue
FROM analytics.mv_order_revenue
GROUP BY
country_name,
region_name,
city_name,
category
UNION ALL
SELECT
country_name,
region_name,
NULL,
category,
SUM(revenue)
FROM analytics.mv_order_revenue
GROUP BY
country_name,
region_name,
category
UNION ALL
SELECT
NULL,
NULL,
NULL,
category,
SUM(revenue)
FROM analytics.mv_order_revenue
GROUP BY
category
UNION ALL
SELECT
NULL,
NULL,
NULL,
NULL,
SUM(revenue)
FROM analytics.mv_order_revenue;| country_name | region_name | city_name | category | total_revenue |
|---|---|---|---|---|
| Georgia | Adjara | Batumi | Internet | 209.93 |
| Georgia | Adjara | Batumi | Cloud | 20.93 |
| Georgia | Tbilisi | Tbilisi | TV | 71.94 |
| Armenia | Yerevan | Yerevan | Internet | 239.92 |
| Armenia | Yerevan | Yerevan | TV | 571.69 |
| Armenia | Shirak | Gyumri | Entertainment | 23.97 |
| Armenia | Lori | Vanadzor | TV | 35.97 |
| Georgia | Adjara | Batumi | Mobile | 174.93 |
| Armenia | Lori | Vanadzor | Entertainment | 71.91 |
| Armenia | Shirak | Gyumri | Cloud | 41.86 |
| Georgia | Tbilisi | Tbilisi | Internet | 284.89 |
| Armenia | Shirak | Gyumri | Internet | 464.83 |
| Georgia | Tbilisi | Tbilisi | Mobile | 59.94 |
| Armenia | Lori | Vanadzor | Mobile | 39.97 |
| Georgia | Adjara | NULL | Internet | 209.93 |
| Georgia | Adjara | NULL | Cloud | 20.93 |
| Georgia | Adjara | NULL | Mobile | 174.93 |
| Georgia | Tbilisi | NULL | TV | 71.94 |
| Georgia | Tbilisi | NULL | Internet | 284.89 |
NULL handlingROLLUP is an advanced aggregation operator that extends GROUP BY to produce hierarchical subtotals automatically.
For a list of grouping columns:
\[ ROLLUP(A, B, C) \]
SQL generates the following grouping levels:
\[ (A,B,C), (A,B), (A), () \]
Typical hierarchies:
We previously solved this with UNION ALL:
All results in one result set.
| country_name | region_name | city_name | total_revenue |
|---|---|---|---|
| Armenia | Lori | Vanadzor | 272.76 |
| Armenia | Lori | NULL | 272.76 |
| Armenia | Shirak | Gyumri | 530.66 |
| Armenia | Shirak | NULL | 530.66 |
| Armenia | Yerevan | Yerevan | 1645.17 |
| Armenia | Yerevan | NULL | 1645.17 |
| Armenia | NULL | NULL | 2448.59 |
| Georgia | Adjara | Batumi | 525.73 |
| Georgia | Adjara | NULL | 525.73 |
| Georgia | Tbilisi | Tbilisi | 461.67 |
| Georgia | Tbilisi | NULL | 461.67 |
| Georgia | NULL | NULL | 987.40 |
| NULL | NULL | NULL | 3435.99 |
Conceptually, the query returns:
(country, region, city) → city-level totals(country, region, NULL) → region subtotals(country, NULL, NULL) → country subtotals(NULL, NULL, NULL) → grand totalAll aggregation levels are computed in one query.
| Aspect | GROUP BY + UNION ALL | ROLLUP |
|---|---|---|
| Number of queries | Many | One |
| Hierarchy expression | Implicit | Explicit |
| Logic duplication | High | None |
| Maintainability | Low | High |
In ROLLUP results:
NULL does not mean missing dataNULL means the column was aggregated awayCUBE is an advanced aggregation operator that generates all possible combinations of the specified grouping columns.
For a list of grouping columns:
\[ CUBE(A, B) \]
SQL generates the following grouping levels:
\[ (A,B), (A), (B), () \]
CUBE is best suited for exploratory analysis.
Typical use cases:
There is no natural hierarchy between dimensions.
We now want to analyze:
All results must appear in one result set.
| country_name | category | total_revenue |
|---|---|---|
| Armenia | NULL | 2448.59 |
| Georgia | Cloud | 41.86 |
| Georgia | Entertainment | 23.97 |
| Georgia | Internet | 494.82 |
| Georgia | Mobile | 234.87 |
| Georgia | TV | 191.88 |
| Georgia | NULL | 987.40 |
| NULL | Cloud | 107.64 |
| NULL | Entertainment | 119.85 |
| NULL | Internet | 1334.48 |
| NULL | Mobile | 1004.48 |
| NULL | TV | 779.54 |
| NULL | NULL | 3435.99 |
Conceptually, the query returns:
(country, category) → detailed cross-sections(country, NULL) → country totals(NULL, category) → category totals(NULL, NULL) → grand totalAll combinations are computed in one query.
| Aspect | ROLLUP | CUBE |
|---|---|---|
| Assumes hierarchy | Yes | No |
| Aggregation pattern | Linear | Combinatorial |
| Result size | Controlled | Potentially large |
| Typical use | Reporting | Exploration |
These limitations motivate the final operator.
GROUPING SETS is the most flexible advanced aggregation operator in SQL.
It allows you to explicitly define which aggregation levels should be computed in a single query.
Unlike ROLLUP and CUBE, nothing is implicit.
General form:
\[ GROUPING\ SETS((A,B),(A),(B),()) \]
Each tuple represents one independent GROUP BY clause.
GROUPING SETS generalizes both operators.
ROLLUP is a special case of GROUPING SETSCUBE is a special case of GROUPING SETSConceptually:
ROLLUP = predefined hierarchical grouping setsCUBE = all possible grouping setsGROUPING SETS = exactly what you ask forRecall the requirement:
This requirement cannot be expressed cleanly with ROLLUP or CUBE.
| country_name | region_name | city_name | category | total_revenue |
|---|---|---|---|---|
| Georgia | Adjara | Batumi | NULL | 525.73 |
| Georgia | Tbilisi | Tbilisi | Cloud | 20.93 |
| Georgia | Tbilisi | Tbilisi | Entertainment | 23.97 |
| Georgia | Tbilisi | Tbilisi | Internet | 284.89 |
| Georgia | Tbilisi | Tbilisi | Mobile | 59.94 |
| Georgia | Tbilisi | Tbilisi | TV | 71.94 |
| Georgia | Tbilisi | NULL | NULL | 461.67 |
| NULL | NULL | NULL | Cloud | 107.64 |
| NULL | NULL | NULL | Entertainment | 119.85 |
| NULL | NULL | NULL | Internet | 1334.48 |
| NULL | NULL | NULL | Mobile | 1004.48 |
| NULL | NULL | NULL | TV | 779.54 |
| NULL | NULL | NULL | NULL | 3435.99 |
Exactly four aggregation levels:
(country, region, city, category) → detailed level(country, region) → region subtotals(category) → category totals() → grand totalNo additional combinations are generated.
| Aspect | UNION ALL | GROUPING SETS |
|---|---|---|
| Number of queries | Many | One |
| Intent clarity | Low | High |
| Maintenance | Difficult | Easy |
| Error risk | High | Low |
| Scalability | Poor | High |
In advanced aggregations, NULL often means aggregated away, not missing.
The GROUPING() function identifies such cases.
SELECT
country_name,
region_name,
city_name,
category,
GROUPING(city_name) AS g_city,
GROUPING(category) AS g_category,
SUM(revenue) AS total_revenue
FROM analytics.mv_order_revenue
GROUP BY GROUPING SETS (
(country_name, region_name, city_name, category),
(country_name, region_name),
(category),
()
);GROUPING(col) = 1 → column aggregated awayGROUPING(col) = 0 → actual value| country_name | region_name | city_name | category | g_city | g_category | total_revenue |
|---|---|---|---|---|---|---|
| Armenia | Lori | Vanadzor | Entertainment | 0 | 0 | 71.91 |
| Georgia | Adjara | Batumi | Cloud | 0 | 0 | 20.93 |
| Armenia | Shirak | Gyumri | Entertainment | 0 | 0 | 23.97 |
| NULL | NULL | NULL | Cloud | 1 | 0 | 107.64 |
| NULL | NULL | NULL | TV | 1 | 0 | 779.54 |
| NULL | NULL | NULL | Entertainment | 1 | 0 | 119.85 |
| NULL | NULL | NULL | Internet | 1 | 0 | 1334.48 |
| NULL | NULL | NULL | Mobile | 1 | 0 | 1004.48 |
| Georgia | Tbilisi | NULL | NULL | 1 | 1 | 461.67 |
| Armenia | Shirak | NULL | NULL | 1 | 1 | 530.66 |
| Armenia | Lori | NULL | NULL | 1 | 1 | 272.76 |
| Armenia | Yerevan | NULL | NULL | 1 | 1 | 1645.17 |
| Georgia | Adjara | NULL | NULL | 1 | 1 | 525.73 |
UNION ALL logicIf you know exactly what you want, use GROUPING SETS.
| Operator | Best Use Case | Main Risk |
|---|---|---|
| ROLLUP | Hierarchies | Rigid |
| CUBE | Exploration | Explosion |
| GROUPING SETS | Production | None |
Advanced aggregations replace procedural SQL with declarative intent.
ROLLUP expresses hierarchyCUBE explores dimensionsGROUPING SETS delivers precision