OLAP Ranking and Top-N

Context: FIT3003_MOC · the most common BI reporting operation after cube/rollup · family map in OLAP (On-Line Analytical Processing) · replaces W7’s rownum top-N in Multi-Fact Star Schemas when ties matter Problem it solves: rank groups by an aggregated measure — overall, within each partition, or cut to the top / top percent.

Quick Revision

  • 🎯 Trigger: “rank”, “top 3”, “best month for each product”, “top 10%” ➔ rank() over (order by sum(m) desc); add partition by for “for each”; wrap in an inline view to keep only the top.
  • ⚡ Key Constraint: the ranking column is computed after where and group by ➔ its alias does not exist in where, so Top-N is always select * from ( … ) where Rank <= N.

🔧 Minimal Working Example

select ProductName,
       sum(Total_Sales) as Total_Sales,
       rank()       over (order by sum(Total_Sales) desc) as Sales_Rank,
       dense_rank() over (order by sum(Total_Sales) desc) as Dense_Rank,
       row_number() over (order by sum(Total_Sales) desc) as Row_Number
from   SalesFact S, ProductDim P, TimeDim T
where  S.ProductID  = P.ProductID
and    S.TimeID     = T.TimeID
and    S.LocationID = 'MEL'
and    Year = 2019
group by ProductName
order by Sales_Rank;

Expected output — the dense_rank slide’s data, where Shoes and Cosmetics tie on (the rank column is derived; the slide’s rank example has no tie):

ProductNameTotal_Salesrankdense_rankrow_number
Clothing715423111
Shoes645881222
Cosmetics645881223
Kids & Baby477949434
Accessories355433545
  • rank ➔ ties share a rank, then the sequence skips ().
  • dense_rank ➔ ties share a rank, no gap ().
  • row_number ➔ never ties: from the order by inside over; tied rows are numbered in arbitrary order.
  • Lab 9a, same pattern on real data ➔ 1996 charter hours per (model, month), rows models months, ascending: 199606 and 199604 tie at ➔ row_number · dense_rank , next · rank , next .
  • No group by needed ➔ over a plain table the window ranks raw rows: rank() over (order by time_year, time_month) as time_rank from dw.time numbers the months chronologically; extra order by keys break ties left to right.
  • desc inside over decides who is ➔ the slide runs rank() over (order by sum(Total_Sales)) (Accessories ) and the desc version (Clothing ) side by side; the outer order by only sorts the display.

🔀 Variations

Top-N — nested query

select * from
  (select ProductName, sum(Total_Sales) as Total_Sales,
          rank() over (order by sum(Total_Sales) desc) as Product_Rank
   from   SalesFact S, TimeDim T, ProductDim P
   where  S.TimeID    = T.TimeID
   and    S.ProductID = P.ProductID
   and    Year = 2019
   group by ProductName)
where Product_Rank <= 2;

Expected output: Clothing (1), Shoes (2).

  • Ties pick the function ➔ on the tie data, <= 2 returns rows under rank or dense_rank (both s kept) but exactly under row_number, which drops one tied product arbitrarily; W7’s rownum idiom behaves like row_number.

Top-percent — percent_rank

  • Value ➔ ; the slide’s ascending run gives Accessories , Kids & Baby , Cosmetics , Shoes , Clothing for .
  • Top , Lab 9a form ➔ keep the ascending over and filter percent_rank >= 1 - p outside the inline view; the mirror form, desc inside over with <= p, returns the same rows.
  • Lab 9a top-10% months ➔ months of revenue, so the steps are ; >= 0.9 keeps ranks – ➔ rows: 199503 (), 199408 (), 199510, 199409, 199703 ().

Rank within groups — partition by

select ProductName, TimeID, sum(Total_Sales) as Total_Sales,
       rank() over (partition by ProductName order by sum(Total_Sales) desc) as Rank_by_Product,
       rank() over (partition by TimeID      order by sum(Total_Sales) desc) as Rank_by_Time
from   SalesFact S, ProductDim P
where  S.ProductID = P.ProductID
and    TimeID in ('201901', '201902', '201903')
and    P.ProductName in ('Clothing', 'Shoes')
group by ProductName, TimeID
order by ProductName, Rank_by_Product;

Expected output:

ProductNameTimeIDTotal_SalesRank_by_ProductRank_by_Time
Clothing20190222425311
Clothing20190120767321
Clothing20190317023832
Shoes20190218278612
Shoes20190317743221
Shoes20190116782032
  • Partition a separate “sheet” ➔ numbering restarts at in each partition: product sheets ranked –, month sheets ranked –.
  • Independent over clauses ➔ two partitionings coexist in one query; each only sees its own rows.

✍️ Practice

⚠️ Common Mistakes

  • 💡 where Product_Rank <= 2 in the same query ➔ the alias is not computed yet (ORA-00904) and a window function is illegal in where ➔ always the inline-view wrapper.
  • 💡 Raw column inside over of a grouped query ➔ over (order by Total_Sales) names the ungrouped column ➔ ORA-00979; order by the aggregate, sum(Total_Sales).
  • 💡 Slide alias Rank_by Time_ID ➔ the space is a syntax error; use Rank_by_Time.