SQL Server 2005+ 引入 ROW_NUMBER() OVER (ORDER BY ...) 窗口函數,配合 CTE 或子查詢,可在單次掃描中同時獲取總記錄數與分頁數據,避免傳統「兩次查詢」或「臨時表」開銷。本文提供標準存儲過程模板與性能分析。
ALTER PROCEDURE usp_GetPagedData
@PageIndex INT = 1, -- 當前頁碼 (1-based)
@PageSize INT = 20, -- 頁大小
@TotalPages INT OUTPUT -- 輸出總頁數
AS
BEGIN
SET NOCOUNT ON;
-- 1. 計算總頁數 (利用 COUNT(*) OVER() 窗口聚合,單掃描)
;WITH Numbered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS RowNum,
COUNT(*) OVER () AS TotalRows -- 窗口聚合,無需額外掃描
FROM dbo.StudentsInfo
)
SELECT @TotalPages = CEILING(TotalRows * 1.0 / @PageSize)
FROM Numbered
WHERE RowNum = 1; -- 任意取第一行即可獲取 TotalRows
-- 2. 返回分頁數據
;WITH Numbered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS RowNum
FROM dbo.StudentsInfo
)
SELECT *
FROM Numbered
WHERE RowNum BETWEEN (@PageIndex - 1) * @PageSize + 1 AND @PageIndex * @PageSize
ORDER BY RowNum;
END;
GO
ALTER PROCEDURE usp_GetPagedData_Optimized
@PageIndex INT = 1,
@PageSize INT = 20,
@TotalRows BIGINT OUTPUT,
@TotalPages INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
;WITH Numbered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS RowNum,
COUNT(*) OVER () AS TotalRows
FROM dbo.StudentsInfo
)
SELECT
@TotalRows = MAX(TotalRows),
@TotalPages = CEILING(MAX(TotalRows) * 1.0 / @PageSize)
FROM Numbered;
;WITH Numbered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS RowNum
FROM dbo.StudentsInfo
)
SELECT *
FROM Numbered
WHERE RowNum BETWEEN (@PageIndex - 1) * @PageSize + 1 AND @PageIndex * @PageSize
ORDER BY RowNum;
END;
注意:上述仍為兩次掃描 (CTE 物化特性)。真正單掃描需應用層處理或 OFFSET/FETCH (2012+)。
ALTER PROCEDURE usp_GetPagedData_2012
@PageIndex INT = 1,
@PageSize INT = 20,
@TotalRows BIGINT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- 總數單獨查詢 (可緩存)
SELECT @TotalRows = COUNT_BIG(*) FROM dbo.StudentsInfo;
-- 分頁查詢:語法簡潔、優化器友好、支持計劃引導
SELECT *
FROM dbo.StudentsInfo
ORDER BY Id
OFFSET (@PageIndex - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
END;
| 方案 | 版本 | 掃描次數 | 深分頁性能 | 可維護性 | 建議場景 |
|---|---|---|---|---|---|
| ROW_NUMBER + CTE | 2005+ | 1-2 | 中 (需掃描前 N 行) | 好 | 2005/2008 兼容、複雜 ORDER BY |
| OFFSET/FETCH | 2012+ | 1+1 (總數) | 中 | 最佳 | 現代版本首選、標準 SQL |
| Keyset Pagination (游標分頁) | All | 1 | 極佳 (無視頁深) | 中 | 無限滾動、API 分頁、深分頁 > 1000 |
| 臨時表 + 身份列 | All | 1 寫 + 1 讀 | 好 | 差 | 極複雜查詢、需多次重用結果集 |
-- 客戶端記住上一頁最後一行的排序鍵值
DECLARE @LastId INT = 10000; -- 上一頁最大 Id
DECLARE @PageSize INT = 20;
SELECT TOP (@PageSize) *
FROM dbo.StudentsInfo
WHERE Id > @LastId -- 唯一遞增鍵
ORDER BY Id;
優勢:索引直接定位,無需掃描前 N 行,毫秒級響應無論頁碼多深。
限制:不支持隨機跳頁、排序列必須唯一、向後翻頁のみ。
ROW_NUMBER() + CTE 標準化分頁。OFFSET/FETCH 簡潔標準,配合緩存總數。TOP N + NOT IN (SELECT TOP M ...) 反模式,性能隨頁碼線性惡化。