Oracle 無 LIMIT/OFFSET(12c 前),分頁查詢依賴 ROWNUM 偽欄或 12c+ FETCH FIRST。本文系統整理 ROWNUM 陷阱、ROW_NUMBER() 分析函數、分組 Top-N、以及「先排序再取」vs「先取再排」的效能與正確性權衡。
SELECT * FROM test WHERE ROWNUM <= 10;
ROWNUM 從 1 開始遞增,**只能用 <= 或 <**,不能用 >=。
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM test a
) WHERE rn BETWEEN 11 AND 20;
內層賦予 ROWNUM 別名 rn,外層才能用 >= 篩選。
SELECT * FROM (
SELECT * FROM torderdetail ORDER BY order_date DESC
) WHERE ROWNUM <= 10;
全表掃描 → 排序 → 取前 10。結果正確,但大表排序耗資源。
SELECT * FROM torderdetail
WHERE ROWNUM <= 10
ORDER BY order_date DESC;
執行順序:**先取 10 筆(隨機/物理順序)→ 再排序**。
ORDER BY 欄位是 **PK/Unique Index**,Oracle 可能優化為「Index Full Scan Desc + Stopkey」,**先排序再取**,結果正確且極快。ORDER BY 非 PK,**先取再排**,結果**錯誤**(非最新 10 筆)。⚠️ 生產環境**嚴禁**使用寫法 B,除非確定排序欄位有唯一索引且經驗證執行計畫出現
COUNT STOPKEY。
SELECT * FROM torderdetail
ORDER BY order_date DESC
FETCH FIRST 10 ROWS ONLY;
-- 分頁:OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY
內部自動轉換為最佳執行計畫,建議 12c+ 專案直接使用。
SELECT * FROM (
SELECT a.*, ROW_NUMBER() OVER (ORDER BY order_date DESC) rn
FROM torderdetail a
) WHERE rn <= 10;
與 ROWNUM 子查詢效能相當,語意更清晰。
ROWNUM 無法實現**,必須用 ROW_NUMBER() + PARTITION BY。
-- 每天取最新 10 筆訂單
SELECT * FROM (
SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY TRUNC(order_date) ORDER BY order_date DESC) rn
FROM torderdetail a
) WHERE rn <= 10;
-- 當月每一天
SELECT TRUNC(SYSDATE,'MM') + ROWNUM - 1 dt
FROM DUAL
CONNECT BY ROWNUM <= TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE),'DD'));
| 場景 | 建議寫法 | 版本 |
|---|---|---|
| 簡單 Top-N | WHERE ROWNUM <= N | 所有 |
| 分頁(固定頁大小) | 子查詢 + ROWNUM rn + BETWEEN | 所有 |
| 分頁(12c+) | OFFSET ... FETCH NEXT ... | 12c+ |
| 分組 Top-N | ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) | 9i+ |
| 需保證正確排序 | 子查詢先 ORDER BY 再 ROWNUM | 所有 |
掌握 ROWNUM 規則與 ROW_NUMBER() 分組能力,即可應付 Oracle 所有分頁與 Top-N 需求,避開「先取再排」的隱形 Bug。