ROWNUM 與 ROWID 同為 Oracle 偽欄位,但本質截然不同。誤用 ROWNUM 是新手常見 Bug 來源。本文深度解析兩者差異、ROWNUM 陷阱成因、正確分頁寫法與 ROWID 實戰應用。
| 特性 | ROWNUM | ROWID |
|---|---|---|
| 本質 | 查詢結果集的**邏輯序號**(1 起) | 資料列在資料檔中的**物理地址**(18 字元 Base64) |
| 存在時機 | 查詢執行時動態產生 | 資料寫入時固定,除非行遷移/分區移動 |
| 可作為 WHERE 條件 | 有嚴格限制(見下文) | 可自由比較、索引存取(隱含索引) |
| 穩定性 | 同一查詢重跑順序不變則相同 | 極度穩定,可跨查詢/Session 定位同一行 |
WHERE ROWNUM > 2 永遠無資料
SELECT * FROM t WHERE ROWNUM > 2; -- 無結果
原因:Oracle 先取第一行 → ROWNUM=1 → 不符合 >2 → 捨棄 → 取第二行 → ROWNUM 重置為 1 → 仍不符合 → 無限循環無結果。
WHERE ROWNUM = 2 也無資料同理,第一行 ROWNUM=1 不符合被捨棄後,後續行 ROWNUM 永遠從 1 開始,永無機會等於 2。
WHERE ROWNUM BETWEEN 2 AND 10 無資料同理,ROWNUM 永遠從 1 起跳,無法跳過第一行。
| 需求 | 正確寫法 | 說明 |
|---|---|---|
| 前 N 筆 | WHERE ROWNUM <= N 或 WHERE ROWNUM < N+1 | 包含 1 即可 |
| 第 M~N 筆(分頁) | 雙層嵌套 + 別名 | 見下方標準分頁模板 |
| 單筆檢查 | WHERE ROWNUM = 1 | 隱性游標常用 |
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM (
SELECT * FROM t ORDER BY id -- 內層必須 ORDER BY 確保順序穩定
) a WHERE ROWNUM <= :end_row
) WHERE rn >= :start_row;
ROWNUM <= :end_row 先截取前端行數,外層再過濾起始行。a,外層引用 rn。OFFSET :start_row ROWS FETCH NEXT :page_size ROWS ONLY。
DELETE FROM t
WHERE ROWID NOT IN (
SELECT MIN(ROWID) FROM t GROUP BY col1, col2, col3 -- 保留每組第一行
);
FOR r IN (SELECT ROWID rid FROM t WHERE cond) LOOP
UPDATE t SET col = val WHERE ROWID = r.rid;
IF MOD(cnt, 1000) = 0 THEN COMMIT; END IF;
END LOOP;
COMMIT;
SELECT * FROM t WHERE ROWID = 'AAAP...'; -- 從 V$SESSION_WAIT 取得 ROWID