OLAP Cube and Rollup

Context: FIT3003_MOC · one query that returns detail rows and their subtotals and grand total · family map in OLAP (On-Line Analytical Processing) Problem it solves: a cross-tab report (product location with row, column and grand totals) without writing and union-ing one group by per total.

Quick Revision

  • 🎯 Trigger: “with subtotals”, “and the grand total”, “cross-tabulation” ➔ group by cube (…) for every combination, group by rollup (…) for the drill path from most detailed to grand total only.
  • ⚡ Key Constraint: count the grouping sets before running ➔ cube of columns gives sets; rollup gives , dropping columns right-to-left, so rollup (A, B) rollup (B, A).

🔧 Minimal Working Example

select   ProductName, Location, sum(Total_Sales) as Total_Sales
from     SalesFact S, ProductDim P, LocationDim D
where    S.ProductID  = P.ProductID
and      S.LocationID = D.LocationID
and      S.LocationID in ('MEL', 'PER')
and      ProductName  in ('Clothing', 'Shoes')
group by cube (ProductName, Location)
order by ProductName, Location;

Expected output — rows detail :

ProductNameLocationTotal_Salesgrouping set
ClothingMelbourne1477348
ClothingPerth861268
Clothing(null)2338616 — product subtotal
ShoesMelbourne1311316
ShoesPerth897153
Shoes(null)2208469 — product subtotal
(null)Melbourne2788664 — cube only
(null)Perth1758421 — cube only
(null)(null)4547085 — grand total
  • (null) means “all” ➔ a null in a dimension column marks that column as aggregated away in this row; ascending order by sorts nulls last, so each subtotal lands under its detail rows.
  • Swap cube ➔ rollup ➔ the same query returns rows: the two rows vanish, leaving product subtotals grand total.
  • Cross-tab reading ➔ the rows are the grid cells, the row totals, the column totals, the corner.

🔀 Variations

Cube vs rollup vs partial — row-count arithmetic

(Chapter 19 example: products locations TimeIDs '201801', '201901')

group by clausegrouping setsrows
cube (P, L, T)all :
rollup (P, L, T):
P, cube (L, T) — partial cube glued onto each set of cube (L, T):
P, rollup (L, T) — partial rollup
  • Rows per set ➔ the number of value combinations of the kept columns that actually occur in the filtered data — at most the of their distinct-value counts, equal only when every combination exists (as in the chapter’s dense example); the empty set is always one row.
  • Cube rollup ➔ every rollup set is also a cube set; the slide strikes rows out of the -row cube to leave exactly the -row rollup.
  • Partial = no grand total ➔ the column outside the parentheses is never rolled up, so every row keeps a real ProductName and no row is all-null.
  • Set-count formula (W10 recap) ➔ counts only the columns inside the parentheses: full cube · full rollup · A, cube (B, C) · A, rollup (B, C) ➔ a fixed column multiplies by : it appears in every set and adds none.

Lab 9a — sparse data and a constant column (CHARTER fuel report)

(dw.charter_fact dw.pilot, Oct–Dec 1995, commercial pilots, model C-90A: periods pilots, but only (period, pilot) pairs flew)

group by clausesets that survive, with rows eachrows
time_id, c.emp_num, mod_code
cube (time_id, c.emp_num, mod_code) · · · · · · ·
cube (time_id, c.emp_num), mod_code · · ·
rollup (time_id, c.emp_num, mod_code) · · ·
rollup (time_id, c.emp_num), mod_code · ·
  • Sparse, not dense ➔ yields rows, not : two pilot–month pairs never occur, so they never appear in any grouping set.
  • A constant column doubles the cube ➔ mod_code holds only 'C-90A', so every set with and its twin without return identical sums ( / , …) ➔ redundant “All Models” rows.
  • The fix is a partial form ➔ move the constant column outside the parentheses — cube (time_id, c.emp_num), mod_code halves , rollup (time_id, c.emp_num), mod_code cuts ; the plain column may sit before or after the cube/rollup term.
  • Grand total ➔ litres in every form ( in the full forms, in the partial ones).

Labelling the subtotal rows — grouping decode

select decode(grouping(ProductName), 1, 'All Products',  ProductName) as Product_Name,
       decode(grouping(Location),    1, 'All Locations', Location)    as Location_Name,
       sum(Total_Sales) as Total_Sales
from   SalesFact S, ProductDim P, LocationDim D
where  S.ProductID  = P.ProductID
and    S.LocationID = D.LocationID
and    S.LocationID in ('MEL', 'PER')
and    ProductName  in ('Clothing', 'Shoes')
group by cube (ProductName, Location)
order by ProductName, Location;
  • grouping(col) ➔ binary: when col is rolled up in this row, when it holds a real group value; on cube (P, L) the pair reads detail · product subtotal · location subtotal · grand total.
  • decode(a, b, c, d) ➔ if then else ➔ (null) becomes 'All Products' / 'All Locations', the rest pass through.
  • Lab form ➔ decode(grouping(c.emp_num), 1, 'All Pilots', c.emp_num) as Pilot — the numeric emp_num is converted to text to share a column with 'All Pilots'.
  • Distinct aliases ➔ Location_Name keeps order by Location pointing at the raw column, so the subtotal rows still sort last.

🔭 Beyond the lecture (not in the slides) ➔ grouping is what separates a subtotal's null from a genuine NULL value stored in a dimension column; is null cannot tell the two apart.

✍️ Practice

⚠️ Common Mistakes

  • 💡 Unqualified LocationID ➔ the slide’s decode example writes and LocationID in (…) with both SalesFact and LocationDim in from ➔ ORA-00918 column ambiguously defined; write S.LocationID.
  • 💡 Reading (null) as missing data ➔ in a cube/rollup result it means “all values of this column”; label it with decode(grouping(…)) before it reaches a report.
  • 💡 Reaching for cube by default ➔ grows fast and fills a hierarchy report with meaningless cross-combinations; a drill path (Year Month, Product Location) is rollup.
  • 💡 Putting a single-valued column inside the cube ➔ every row appears twice with the same sum (Lab 9a’s “All Models”); filter-fixed columns belong outside the parentheses as a partial cube/rollup.