CREATE PROCEDURE dbo.Pagination
@tblName VARCHAR(255), -- 表名
@strGetFields VARCHAR(1000) = '*', -- 需回傳欄位
@fldName VARCHAR(255) = '', -- 排序欄位
@PageSize INT = 10, -- 頁大小
@PageIndex INT = 1, -- 頁碼(從 1 開始)
@doCount BIT = 0, -- 是否回傳總筆數
@OrderType BIT = 0, -- 0=ASC, 1=DESC
@strWhere VARCHAR(1500) = '' -- WHERE 條件(不含 WHERE 關鍵字)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @strSQL VARCHAR(5000),
@strTmp VARCHAR(110),
@strOrder VARCHAR(400);
-- 1. 僅回傳總筆數
IF @doCount <> 0
BEGIN
IF @strWhere <> ''
SET @strSQL = 'SELECT COUNT(*) AS Total FROM [' + @tblName + '] WHERE ' + @strWhere;
ELSE
SET @strSQL = 'SELECT COUNT(*) AS Total FROM [' + @tblName + ']';
EXEC(@strSQL);
RETURN;
END
-- 2. 組裝排序與比較邏輯
IF @OrderType <> 0
BEGIN
SET @strTmp = '< (SELECT MIN(';
SET @strOrder = ' ORDER BY [' + @fldName + '] DESC';
END
ELSE
BEGIN
SET @strTmp = '> (SELECT MAX(';
SET @strOrder = ' ORDER BY [' + @fldName + '] ASC';
END
-- 3. 第一頁:直接 TOP N(最快)
IF @PageIndex = 1
BEGIN
IF @strWhere <> ''
SET @strSQL = 'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' ' + @strGetFields +
' FROM [' + @tblName + '] WHERE ' + @strWhere + @strOrder;
ELSE
SET @strSQL = 'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' ' + @strGetFields +
' FROM [' + @tblName + ']' + @strOrder;
EXEC(@strSQL);
RETURN;
END
-- 4. 非第一頁:子查詢跳過前 N 頁
-- 核心技巧:WHERE 排序欄位 < (SELECT MIN/MAX 排序欄位 FROM 前 N 頁)
SET @strSQL = 'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' ' + @strGetFields +
' FROM [' + @tblName + '] ' +
'WHERE [' + @fldName + ']' + @strTmp + '[' + @fldName + '] ' +
'FROM (SELECT TOP ' + CAST((@PageIndex-1)*@PageSize AS VARCHAR) +
' [' + @fldName + '] FROM [' + @tblName + ']' + @strOrder + ') AS tblTmp)' + @strOrder;
IF @strWhere <> ''
SET @strSQL = 'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' ' + @strGetFields +
' FROM [' + @tblName + '] ' +
'WHERE [' + @fldName + ']' + @strTmp + '[' + @fldName + '] ' +
'FROM (SELECT TOP ' + CAST((@PageIndex-1)*@PageSize AS VARCHAR) +
' [' + @fldName + '] FROM [' + @tblName + '] WHERE ' + @strWhere + @strOrder +
') AS tblTmp) AND ' + @strWhere + @strOrder;
EXEC(@strSQL);
END;
GO
| 分頁方式 | 適用頁碼 | 原理 | 效能特點 |
|---|---|---|---|
| 直接 TOP N | 第 1 頁 | 單一掃描取前 N 筆 | 最快,無子查詢開銷 |
| 子查詢 MAX/MIN 跳過法 | 第 2 頁+ | 子查詢取前 (頁碼-1)*頁大小 筆的極值,外層以極值為界 | 避免 OFFSET 掃描,需排序欄位有索引且唯一/高選擇性 |
WHERE (SortCol, PK) > (SELECT MAX(SortCol), MAX(PK) FROM ...)@strWhere 不含 WHERE 關鍵字,直接寫條件(如 status=1 AND type=2)。
-- OFFSET/FETCH 標準語法,簡潔且優化器支援更好
DECLARE @PageSize INT = 10, @PageIndex INT = 3;
SELECT col1, col2, col3
FROM dbo.MyTable
WHERE status = 1
ORDER BY created_date DESC
OFFSET (@PageIndex - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
WHERE created_date < @last_seen_date ORDER BY created_date DESC LIMIT @PageSizeCREATE INDEX IX_Table_SortCol_Includes ON Table(SortCol) INCLUDE (col1, col2, col3); 避免書籤查找。@strGetFields。sp_executesql 而非 EXEC(@sql),重用計畫、防注入。@doCount=1 獨立查詢,前端快取總筆數,避免每頁重複 COUNT(*)。
-- 第 3 頁,每頁 20 筆,按 id 降序,條件 status=1
EXEC dbo.Pagination
@tblName = 'Orders',
@strGetFields = 'id, customer_id, amount, created_date',
@fldName = 'id',
@PageSize = 20,
@PageIndex = 3,
@OrderType = 1,
@strWhere = 'status = 1';
-- 取得總筆數
EXEC dbo.Pagination @tblName = 'Orders', @doCount = 1, @strWhere = 'status = 1';
此分頁預存程序展示了 SQL Server 2000/2005 時代的經典動態 SQL 分頁技巧,現代專案建議直接使用 OFFSET/FETCH 或 Keyset Pagination。