Oracle ROLLUP, CUBE, and GROUPING SETS Version 21c
General Information
Library Note
Morgan's Library Page Header
ACE Director Alum Daniel Morgan, founder of Morgan's Library, is scheduling
complimentary technical Workshops on Database Security for the first 30
Oracle Database customers located anywhere in North America, EMEA, LATAM, or
APAC that send an email to
asra_us@oracle.com. Request a Workshop for
your organization today.
Purpose
Rollup Note: ROLLUP enables a SELECT statement to calculate multiple levels of subtotals across a specified group of dimensions.
It also calculates a grand total. ROLLUP is a simple extension to the GROUP BY clause, so its syntax is extremely easy to use. The ROLLUP extension is highly efficient, adding minimal overhead to a query.
The action of ROLLUP is straight forward: it creates subtotals that roll up from the most detailed level to a grand total, following a grouping list specified in the ROLLUP clause. ROLLUP takes as its argument an ordered list of grouping columns.
First, it calculates the standard aggregate values specified in the GROUP BY clause. Then, it creates progressively higher-level subtotals, moving from right to left through the list of grouping columns. Finally, it creates a grand total.
ROLLUP creates subtotals at n+1 levels, where n is the number of grouping columns. For instance, if a query specifies ROLLUP on grouping columns of time, region, and department (n=3), the result set will include rows at four aggregation levels.
Cube Note: CUBE takes a specified set of grouping columns and creates subtotals for all of their possible combinations.
In terms of multidimensional analysis, CUBE generates all the subtotals that could be calculated for a data cube with the specified dimensions.
If you have specified CUBE(time, region, department), the result set will include all the values that would be included in an equivalent ROLLUP statement plus additional combinations.
Consider Using CUBE in any situation requiring cross-tabular reports. The data needed for cross-tabular reports can be generated with a single SELECT using CUBE. Like ROLLUP, CUBE can be helpful in generating summary tables.
Note that population of summary tables is even faster if the CUBE query executes in parallel.
CUBE is typically most suitable in queries that use columns from multiple dimensions rather than columns representing different levels of a single dimension.
For instance, a commonly requested cross-tabulation might need subtotals for all the combinations of month, state, and product. These are three independent dimensions, and analysis of all possible subtotal combinations is commonplace.
In contrast, a cross-tabulation showing all possible combinations of year, month, and day would have several values of limited interest, because there is a natural hierarchy in the time dimension.
Subtotals such as profit by day of month summed across year would be unnecessary in most analyses.
Create demo schema
To obtain the Sales History (SH) schema in 12cR1 and above you must check the "Sample Schemas" checkbox during DBCA installation which creates the ORCL PDB.
SELECT ch.channel_desc, t.calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND cu.country_id = co.country_id
AND ch.channel_desc IN ('Direct Sales','Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY ROLLUP(ch.channel_desc, t.calendar_month_desc, co.country_name);
Partial Rollup Demo SQL Statement
conn sh/sh@pdbdev
SELECT ch.channel_desc, t.calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc IN ('Direct Sales','Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY ch.channel_desc, ROLLUP(t.calendar_month_desc, co.country_name);
Distinguishes super-aggregate rows from regular grouped rows
Distinguish a null representing the set of all values in a super-aggregate row from a null in a regular row
GROUPING(v IN VARCHAR2) RETURN NUMBER;
GROUPING(a IN "<ADT_1>") RETURN NUMBER;
GROUPING(a IN "<OPAQUE_1>") RETURN NUMBER;
conn scott/tiger@pdbdev
SELECT deptno, job, SUM(sal)
FROM emp
GROUP BY ROLLUP(deptno, job);
SELECT deptno, job, SUM(sal), GROUPING(deptno), GROUPING(job)
FROM emp
GROUP BY ROLLUP(deptno, job);
conn hr/hr@pdbdev
col job format a10
SELECT DECODE(department_name, '1', 'All Departments',
department_name) AS DEPARTMENT,
DECODE(job_id, '1', 'All Jobs', job_id) AS job,
COUNT(*) "Total Empl", AVG(salary) * 12 "Average Sal"
FROM employees e, departments d
WHERE d.department_id = e.department_id
GROUP BY ROLLUP (department_name, job_id);
SELECT DECODE(GROUPING(department_name), '1', 'All Departments',
department_name) AS DEPARTMENT,
DECODE(GROUPING(job_id), '1', 'All Jobs', job_id) AS job,
COUNT(*) "Total Empl", AVG(salary) * 12 "Average Sal"
FROM employees e, departments d
WHERE d.department_id = e.department_id
GROUP BY ROLLUP (department_name, job_id);
SELECT channel_desc, calendar_month_desc, co.country_id,
TO_CHAR(SUM(amount_sold) , '9,999,999,999') SALES$
FROM sales, customers, times, channels, countries co
WHERE sales.time_id=times.time_id
AND sales.cust_id=customers.cust_id
AND sales.channel_id= channels.channel_id
AND customers.country_id = co.country_id
AND channels.channel_desc IN ('Direct Sales', 'Internet')
AND times.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_iso_code IN ('UK', 'US')
GROUP BY GROUPING SETS(
(channel_desc, calendar_month_desc, co.country_id),
(channel_desc, co.country_id),
(calendar_month_desc, co.country_id));
SELECT channel_desc, calendar_month_desc, co.country_id,
TO_CHAR(SUM(amount_sold) , '9,999,999,999') SALES$
FROM sales, customers, times, channels, countries co
WHERE sales.time_id=times.time_id
AND sales.cust_id=customers.cust_id
AND sales.channel_id= channels.channel_id
AND customers.country_id = co.country_id
AND channels.channel_desc IN ('Direct Sales', 'Internet')
AND times.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_iso_code IN ('UK', 'US')
GROUP BY CUBE(channel_desc, calendar_month_desc, co.country_id);
CUBE
Full Cube Rollup
GROUP BY CUBE()
conn scott/tiger@pdbdev
SELECT deptno, job, SUM(sal)
FROM emp
GROUP BY CUBE(deptno, job)
ORDER BY 1 NULLS LAST;
Explanation
DEPTNO
JOB
SUM(SAL)
10
CLERK
1300
10
MANAGER
2450
10
PRESIDENT
5000
Total salary for department 10
10
8750
20
ANALYST
6000
20
CLERK
1900
20
MANAGER
2975
Total salary for department 20
20
10875
30
CLERK
950
30
MANAGER
2850
30
SALESMAN
5600
Total salary for department 30
30
9400
Total salary by job for analysts
ANALYST
6000
Total salary by job for clerks
CLERK
4150
Total salary by job for managers
MANAGER
8275
Total salary by job for the president
PRESIDENT
5000
Total salary by job for salesmen
SALESMAN
560
Total salary all jobs and departments
29025
conn sh/sh@pdbdev
col sales$ format a20
SELECT ch.channel_desc, calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc IN ('Direct Sales', 'Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY CUBE(channel_desc, t.calendar_month_desc, co.country_name);
Partial Cube Rollup
conn sh/sh@pdbdev
SELECT ch.channel_desc, calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc IN ('Direct Sales', 'Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY channel_desc, CUBE(t.calendar_month_desc, co.country_name);
Returns a number corresponding to the GROUPING bit vector associated with a row.
In queries with many GROUP BY expressions, determining the GROUP BY level of a particular row requires many GROUPING functions, which leads to cumbersome SQL. GROUPING_ID is useful in these cases.
GROUPING_ID(<expression>, <expression>, ..)
conn sh/sh@pddorcl
SELECT channel_id, promo_id, SUM(amount_sold) s_sales,
GROUPING(channel_id) gc,
GROUPING(promo_id) gp
FROM sales
WHERE promo_id > 496
GROUP BY CUBE(channel_id, promo_id);
SELECT channel_id, promo_id, SUM(amount_sold) s_sales,
GROUPING(channel_id) AS GC,
GROUPING(promo_id) AS GP,
GROUPING_ID(channel_id, promo_id) AS GCP,
GROUPING_ID(promo_id, channel_id) AS GPC
FROM sales
WHERE promo_id > 496
GROUP BY CUBE(channel_id, promo_id);