Oracle 無 LIMIT/TOP 關鍵字,使用虛擬欄位 ROWNUM 達成分頁。關鍵規則:ROWNUM 是在 **WHERE 過濾後、ORDER BY 排序前** 依序分配的流水號(從 1 開始)。
SELECT * FROM emp WHERE ROWNUM <= 10;
-- 等同 MySQL: LIMIT 10
-- 錯誤:無資料回傳
SELECT * FROM emp WHERE ROWNUM > 10 AND ROWNUM <= 20;
-- 原因:第一列 ROWNUM=1 不符合 >10 被過濾,後續列永無機會獲得 ROWNUM=2...
-- 取第 11-20 筆 (頁碼 2, 每頁 10)
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM (
SELECT * FROM emp ORDER BY empno -- 內層先排序
) a WHERE ROWNUM <= 20 -- 中層截取前 20
) WHERE rn > 10; -- 外層篩選 11-20
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM (
SELECT * FROM large_table
WHERE filter_col = :val
ORDER BY sort_col
) a WHERE ROWNUM <= :page_size * :page_num
) WHERE rn > :page_size * (:page_num - 1);
SELECT * FROM emp
ORDER BY empno
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
-- 等同 LIMIT 10 OFFSET 10
優點:語法清晰、最佳化器可直接轉換為分頁執行計畫、支援 FETCH FIRST n ROWS WITH TIES 處理並列排名。
| 方法 | 適用版本 | 大資料量效能 | 備註 |
|---|---|---|---|
| ROWNUM 巢狀查詢 | 8i+ | 中等 (需掃描至上限) | 配合索引排序可避免全表掃描 |
| ROWNUM + FIRST_ROWS(n) Hint | 8i+ | 較好 | /*+ FIRST_ROWS(10) */ 引導最佳化器選擇 Nested Loop |
| OFFSET FETCH | 12c+ | 最佳 | 內部最佳化,可推導 predicate pushdown |
| 游標分頁 (PL/SQL) | 所有 | 最佳 (串流) | 應用層逐頁 FETCH,避免重複掃描 |
rn、內層 ROWNUM,勿混淆。OPT_PARAM('_optim_peek_user_binds','FALSE') 或 BIND_AWARE Hint。-- 目標:emp 表 100 萬筆,依 hiredate DESC 分頁,每頁 20 筆,取第 50 頁
SELECT * FROM (
SELECT /*+ FIRST_ROWS(1000) INDEX(e emp_hiredate_idx) */
e.*, ROWNUM rn
FROM (
SELECT * FROM emp e
WHERE deptno = 10
ORDER BY hiredate DESC
) e
WHERE ROWNUM <= 1000 -- 50 * 20
) WHERE rn > 980; -- (50-1)*20
Oracle 分頁核心在於 **「內層排序 → 中層截取上限 → 外層篩選下限」** 三層結構。12c+ 請直接使用 OFFSET FETCH;舊版本務必掌握 ROWNUM 三層巢狀寫法,並善用 FIRST_ROWS(n) Hint 與排序索引,將分頁查詢穩定在毫秒級。