Oracle 分析函數深度解析:RANK、ROW_NUMBER、LAG/LEAD 實戰應用
Oracle 分析函數 (Analytic Functions) 是處理複雜排序、分組統計、同比環比計算的利器。本文系統整理 RANK、DENSE_RANK、ROW_NUMBER、LAG/LEAD 核心語法、行為差異與實戰範例,助您精準驾驭資料分析需求。
核心函數對照表
| 函數 | 用途 | 並列處理 | 典型場景 |
| RANK() | 跳躍排序 | 並列後跳過名次 (1,1,3) | 競賽排名、績效分級 |
| DENSE_RANK() | 連續排序 | 並列後不跳過 (1,1,2) | 密集評分、分頁去重 |
| ROW_NUMBER() | 唯一流水號 | 強制唯一 (1,2,3,4) | 分頁查詢、Top-N、去重保留首筆 |
| LAG() | 向前看 (上一列) | 取前 N 列值 | 環比計算、差異比較 |
| LEAD() | 向後看 (下一列) | 取後 N 列值 | 同比計算、趨勢預測 |
通用語法結構
FUNCTION_NAME ( [arguments] ) OVER (
[ PARTITION BY partition_column ] -- 分組,可省略 = 全表單一分組
ORDER BY sort_column [ASC|DESC] [NULLS FIRST|LAST] -- 排序,必填
[ WINDOWING_CLAUSE ] -- 視窗範圍,進階用法
)
實戰範例 1:RANK vs DENSE_RANK 差異示範
-- 測試資料
WITH sales AS (
SELECT '2023-Q1' quarter, 'East' region, 100 amount FROM dual UNION ALL
SELECT '2023-Q1', 'West', 100 FROM dual UNION ALL
SELECT '2023-Q1', 'North', 90 FROM dual UNION ALL
SELECT '2023-Q2', 'East', 120 FROM dual UNION ALL
SELECT '2023-Q2', 'West', 120 FROM dual UNION ALL
SELECT '2023-Q2', 'South', 80 FROM dual
)
SELECT
quarter, region, amount,
RANK() OVER (PARTITION BY quarter ORDER BY amount DESC) AS rk,
DENSE_RANK() OVER (PARTITION BY quarter ORDER BY amount DESC) AS drk
FROM sales
ORDER BY quarter, rk;
結果對照
| QUARTER | REGION | AMOUNT | RANK | DENSE_RANK |
| 2023-Q1 | East | 100 | 1 | 1 |
| 2023-Q1 | West | 100 | 1 | 1 |
| 2023-Q1 | North | 90 | 3 | 2 |
| 2023-Q2 | East | 120 | 1 | 1 |
| 2023-Q2 | West | 120 | 1 | 1 |
| 2023-Q2 | South | 80 | 3 | 2 |
實戰範例 2:ROW_NUMBER 實現分頁與 Top-N
場景 A:標準分頁查詢 (第 2 頁,每頁 10 筆)
SELECT * FROM (
SELECT e.*, ROW_NUMBER() OVER (ORDER BY hire_date DESC) AS rn
FROM employees e
) WHERE rn BETWEEN 11 AND 20;
場景 B:各部門薪資前 3 高 (Top-N per Group)
SELECT * FROM (
SELECT
e.department_id, e.employee_id, e.salary,
ROW_NUMBER() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS rn
FROM employees e
) WHERE rn <= 3;
場景 C:去重保留最新記錄
DELETE FROM logs
WHERE rowid IN (
SELECT rowid FROM (
SELECT rowid, ROW_NUMBER() OVER (
PARTITION BY user_id, action_type
ORDER BY created_time DESC
) AS rn
FROM logs
) WHERE rn > 1
);
實戰範例 3:LAG/LEAD 同比環比計算
建立測試資料:訂單流程追蹤
CREATE TABLE order_flow (
case_id VARCHAR2(10),
step_id VARCHAR2(10),
action_dt DATE
);
INSERT INTO order_flow VALUES ('Case1','Step1',DATE'2023-01-01');
INSERT INTO order_flow VALUES ('Case1','Step2',DATE'2023-01-02');
INSERT INTO order_flow VALUES ('Case1','Step3',DATE'2023-01-03');
INSERT INTO order_flow VALUES ('Case1','Step4',DATE'2023-01-04');
INSERT INTO order_flow VALUES ('Case1','Step5',DATE'2023-01-05');
INSERT INTO order_flow VALUES ('Case1','Step4',DATE'2023-01-06'); -- 返工
INSERT INTO order_flow VALUES ('Case1','Step6',DATE'2023-01-07');
INSERT INTO order_flow VALUES ('Case2','Step1',DATE'2023-02-01');
INSERT INTO order_flow VALUES ('Case2','Step2',DATE'2023-02-02');
INSERT INTO order_flow VALUES ('Case2','Step3',DATE'2023-02-03');
COMMIT;
查詢:每步驟與前/後步驟的天數差異
SELECT
case_id, step_id, action_dt,
LAG(step_id) OVER (PARTITION BY case_id ORDER BY action_dt) AS prev_step,
LAG(action_dt) OVER (PARTITION BY case_id ORDER BY action_dt) AS prev_dt,
LEAD(step_id) OVER (PARTITION BY case_id ORDER BY action_dt) AS next_step,
LEAD(action_dt) OVER (PARTITION BY case_id ORDER BY action_dt) AS next_dt,
LEAD(action_dt) OVER (PARTITION BY case_id ORDER BY action_dt) - action_dt AS days_diff
FROM order_flow
ORDER BY case_id, action_dt;
結果示例
| CASE_ID | STEP_ID | ACTION_DT | PREV_STEP | PREV_DT | NEXT_STEP | NEXT_DT | DAYS_DIFF |
| Case1 | Step1 | 2023-01-01 | (null) | (null) | Step2 | 2023-01-02 | 1 |
| Case1 | Step2 | 2023-01-02 | Step1 | 2023-01-01 | Step3 | 2023-01-03 | 1 |
| Case1 | Step3 | 2023-01-03 | Step2 | 2023-01-02 | Step4 | 2023-01-04 | 1 |
| Case1 | Step4 | 2023-01-04 | Step3 | 2023-01-03 | Step5 | 2023-01-05 | 1 |
| Case1 | Step5 | 2023-01-05 | Step4 | 2023-01-04 | Step4 | 2023-01-06 | 1 |
| Case1 | Step4 | 2023-01-06 | Step5 | 2023-01-05 | Step6 | 2023-01-07 | 1 |
| Case1 | Step6 | 2023-01-07 | Step4 | 2023-01-06 | (null) | (null) | (null) |
進階語法:聚合函數語法 (9i+)
-- 計算特定值在排序中的位置
SELECT RANK(4, 3) WITHIN GROUP (ORDER BY col1, col2) AS pos FROM table;
-- 等價於:WHERE col1=4 AND col2=3 在排序後的排名
效能優化關鍵點
- 索引支援:
PARTITION BY + ORDER BY 欄位建立複合索引可避免全表排序
- 減少分區數:大量資料時,過多 PARTITION 會增加排序記憶體壓力
- 避免 SELECT *:僅選取必要欄位,減少排序資料量
- 物化視圖預計算:固定報表可預先計算排名結果
常見錯誤與修正
| 錯誤寫法 | 問題 | 正確寫法 |
| RANK() OVER (ORDER BY sal) WHERE RANK <= 3 | 分析函數不可直接用於 WHERE | 使用子查詢/Inline View 包裹 |
| LAG(sal) OVER (ORDER BY hiredate) | 未指定 PARTITION,跨部門比較無意義 | 加上 PARTITION BY deptno |
| ROW_NUMBER() OVER (PARTITION BY deptno) | 缺少 ORDER BY,結果不確定 | 必須指定 ORDER BY 子句 |
| LEAD(sal, 2, 0) -- 忽略 DEFAULT 參數位置 | 參數順序錯誤 | LEAD(sal, 2, 0) -- expr, offset, default |
分析函數將「行間運算」SQL 化,取代傳統自連接/子查詢/游標邏輯,不僅程式碼簡潔,更能利用 Oracle 內部排序優化引擎。掌握 RANK 族、ROW_NUMBER、LAG/LEAD 五大核心函數,即可覆蓋 90% 以上的分析型查詢需求。