在早期不支援窗口函數的 SQL 版本中,利用「自我聯結 + 聚合計數」是實現排名的經典技巧。本文以銷售資料為例,示範如何撰寫排名查詢,並延伸至「查詢第 N 名」的進階寫法。
假設資料表 Total_Sales 包含兩欄:Name(業務員)、Sales(業績)。需求:依 Sales 降冪排序,產生 Sales_Rank 排名欄位。
-- 寫法 A:允許同分同排名(密集排名 Dense Rank 效果)
SELECT a1.Name, a1.Sales, COUNT(a2.Sales) AS Sales_Rank
FROM Total_Sales a1
JOIN Total_Sales a2
ON a1.Sales <= a2.Sales
OR (a1.Sales = a2.Sales AND a1.Name = a2.Name) -- 同分時以 Name 破 tie
GROUP BY a1.Name, a1.Sales
ORDER BY a1.Sales DESC, a1.Name DESC;
原理說明:對每一筆 a1,去數有多少筆 a2 的業績「大於或等於」它。計數結果即為排名。若業績相同、名字也相同,視為同一筆資料,避免重複計數。
-- 寫法 B:同分不佔排名(競爭排名 Rank 效果,如 1,2,2,4)
SELECT a1.Name, a1.Sales, COUNT(a2.Sales) AS Sales_Rank
FROM Total_Sales a1
JOIN Total_Sales a2
ON a1.Sales < a2.Sales
OR (a1.Sales = a2.Sales AND a1.Name = a2.Name)
GROUP BY a1.Name, a1.Sales
ORDER BY a1.Sales DESC, a1.Name DESC;
差別在於 <= 改為 <,同分者不會互相計數,導致排名出現間隙。
SELECT * FROM (
SELECT a1.Name, a1.Sales, COUNT(a2.Sales) AS Sales_Rank
FROM Total_Sales a1
JOIN Total_Sales a2
ON a1.Sales < a2.Sales OR (a1.Sales = a2.Sales AND a1.Name = a2.Name)
GROUP BY a1.Name, a1.Sales
) AS ranked
WHERE Sales_Rank = 5;
若資料庫支援窗口函數,請直接使用 RANK()、DENSE_RANK()、ROW_NUMBER(),語法更簡潔、效能更佳:
SELECT Name, Sales,
RANK() OVER (ORDER BY Sales DESC) AS Sales_Rank
FROM Total_Sales;
自我聯結排名雖屬經典技巧,但在大量資料下效能較差(O(N²)),實務上請優先考慮窗口函數或資料庫原生排名功能。