SQL Server 2005+ 支援 ROW_NUMBER() OVER (ORDER BY ...),配合 CTE(Common Table Expression)即可實現高效分頁,無需臨時表或雙重排序。
CREATE PROCEDURE dbo.usp_GetNewsPaged
@PageSize INT,
@PageIndex INT -- 0-based:第 0 頁 = 第一頁
AS
BEGIN
SET NOCOUNT ON;
;WITH Numbered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS RowNum
FROM dbo.News
)
SELECT *
FROM Numbered
WHERE RowNum BETWEEN (@PageIndex * @PageSize + 1) AND ((@PageIndex + 1) * @PageSize)
ORDER BY RowNum;
END
GO
@PageIndex = 0 代表第一頁,符合大多數程式語言習慣(Java、C#、JS)。BETWEEN 區間:@PageIndex * @PageSize + 1(@PageIndex + 1) * @PageSizeROW_NUMBER() OVER (ORDER BY Id) ✅SELECT ... FROM News ORDER BY Id ❌(外層 ORDER BY 不影響 ROW_NUMBER 排序)RowNum 不可在同層 WHERE 使用:必須包裹在 CTE/子查詢中,因視窗函數在 WHERE 階段尚未計算。
String sql = "{call usp_GetNewsPaged(?, ?)}";
try (Connection conn = dataSource.getConnection();
CallableStatement cs = conn.prepareCall(sql)) {
cs.setInt(1, 20); // pageSize
cs.setInt(2, 0); // pageIndex (第一頁)
try (ResultSet rs = cs.executeQuery()) {
while (rs.next()) {
// 處理欄位...
}
}
}
| 場景 | 建議 |
|---|---|
| 資料量 < 10 萬、單表 | 上述 CTE 寫法足夠,配合 ORDER BY 欄位建立索引。 |
| 資料量 > 100 萬、深分頁(第 1000 頁) | 改用 Keyset Pagination(游標式):WHERE Id > @LastMaxId ORDER BY Id OFFSET 0 ROWS FETCH NEXT @PageSize ROWS ONLY (SQL 2012+) |
| 複雜 JOIN、多表排序 | 將排序鍵與主鍵組合成覆蓋索引,或物化為暫存表再分頁。 |
-- ❌ 錯誤:WHERE 直接用 ROW_NUMBER 別名
SELECT *, ROW_NUMBER() OVER (ORDER BY Id) AS RN FROM News WHERE RN BETWEEN 1 AND 20;
-- ✅ 正確:包裹在 CTE/子查詢
;WITH T AS (SELECT *, ROW_NUMBER() OVER (ORDER BY Id) AS RN FROM News)
SELECT * FROM T WHERE RN BETWEEN 1 AND 20;
ROW_NUMBER() + CTE 是 SQL Server 分頁的經典標準寫法,語法清晰、效能穩定、易於封裝為預存程序供應用層呼叫。掌握「視窗函數在 WHERE 之後計算、ORDER BY 在 OVER 內指定、索引配合排序鍵」三要素,即能寫出正確且高效的分頁查詢。