Web 應用常需分頁顯示大量資料。雖然各資料庫提供專屬分頁語法(SQL Server OFFSET/FETCH、MySQL LIMIT、Oracle ROWNUM),但掌握純 ANSI SQL 分頁寫法可提高程式碼可移植性,也有助於理解分頁本質。本文示範以相關子查詢計算序數的標準做法。
分頁本質是「取得第 N 到 M 筆依排序鍵排序的列」。標準 SQL 無內建序數函數(SQL:2003 引入 ROW_NUMBER() 但早期版本不支援),可用相關子查詢計算「比目前列排序鍵小的列數」作為序數。
SELECT *
FROM t_table t1
WHERE (SELECT COUNT(*) FROM t_table t2 WHERE t2.id < t1.id) >= 10
AND (SELECT COUNT(*) FROM t_table t2 WHERE t2.id < t1.id) < 20
ORDER BY t1.id;
t2.id < t1.id 計算嚴格小於目前列 ID 的列數 → 0-based 序數。>= 10 AND < 20 對應第 11~20 筆(1-based 頁碼)。SELECT *
FROM t_table t1
WHERE (SELECT COUNT(*) FROM t_table t2 WHERE t2.id < t1.id AND t2.key = '123') >= 10
AND (SELECT COUNT(*) FROM t_table t2 WHERE t2.id < t1.id AND t2.key = '123') < 20
AND t1.key = '123'
ORDER BY t1.id;
子查詢與外層需完全一致的篩選條件,否則序數計算會錯誤。
| 問題 | 影響 | 對策 |
|---|---|---|
| 相關子查詢逐列執行 | O(N²) 複雜度,資料量大極慢 | 僅適用小資料集(< 1 萬列)或教學示範 |
| 無索引支援 | 全表掃描 | 排序鍵(id)與篩選鍵(key)建立複合索引 |
| 重複計算 | 兩個子查詢邏輯相同 | 使用 CTE/子查詢預先計算序數 |
WITH Numbered AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM t_table
WHERE key = '123'
)
SELECT * FROM Numbered WHERE rn BETWEEN 11 AND 20;
SELECT * FROM t_table
WHERE key = '123'
ORDER BY id
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
ROW_NUMBER() / OFFSET/FETCH / LIMIT 等原生分頁語法。