Web 應用中分頁瀏覽是核心功能,但傳統 ADO 記錄集分頁(基於游標)在大數據量下存在嚴重效能瓶頸:游標佔用大量記憶體、鎖定記錄、多表大表查詢易導致程式無回應。現代分頁核心理念是「只檢索當前頁面大小的資料塊,而非載入全部資料」。
早期較好的實現方式,但受限於游標缺點,未獲廣泛認可。
CREATE PROCEDURE pagination1
@pagesize INT, -- 頁面大小
@pageindex INT -- 當前頁碼
AS
BEGIN
SET NOCOUNT ON
DECLARE @indextable TABLE(id INT IDENTITY(1,1), nid INT) -- 表變量
DECLARE @PageLowerBound INT, @PageUpperBound INT
SET @PageLowerBound = (@pageindex - 1) * @pagesize
SET @PageUpperBound = @PageLowerBound + @pagesize
SET ROWCOUNT @PageUpperBound
INSERT INTO @indextable(nid)
SELECT gid FROM TGongwen
WHERE fariqi > DATEADD(day, -365, GETDATE())
ORDER BY fariqi DESC
SELECT O.gid, O.mid, O.title, O.fadanwei, O.fariqi
FROM TGongwen O, @indextable t
WHERE O.gid = t.nid
AND t.id > @PageLowerBound AND t.id <= @PageUpperBound
ORDER BY t.id
SET NOCOUNT OFF
END
優點:使用表變量優於暫存表,速度優於 ADO 方式。但後續發現更優方案。
核心邏輯源於「從資料表取出第 n 到第 m 條記錄」:
SELECT TOP m-n+1 *
FROM publish
WHERE id NOT IN (SELECT TOP n-1 id FROM publish)
改造為分頁存儲過程:
CREATE PROCEDURE pagination2
@SQL NVARCHAR(4000), -- 不含 ORDER BY 的 SQL
@Page INT, -- 頁碼
@RecsPerPage INT, -- 每頁筆數
@ID VARCHAR(255), -- 排序用不重複 ID
@Sort VARCHAR(255) -- 排序欄位與規則
AS
BEGIN
DECLARE @Str NVARCHAR(4000)
SET @Str = 'SELECT TOP ' + CAST(@RecsPerPage AS VARCHAR(20)) +
' * FROM (' + @SQL + ') T ' +
'WHERE T.' + @ID + ' NOT IN (' +
'SELECT TOP ' + CAST((@RecsPerPage * (@Page - 1)) AS VARCHAR(20)) +
' ' + @ID + ' FROM (' + @SQL + ') T9 ORDER BY ' + @Sort +
') ORDER BY ' + @Sort
PRINT @Str
EXEC sp_ExecuteSql @Str
END
簡化邏輯:
SELECT TOP 頁大小 *
FROM Table1
WHERE ID NOT IN (SELECT TOP 頁大小*頁數 ID FROM 表 ORDER BY ID)
ORDER BY ID
NOT IN,大數據量下效能極差NOT EXISTS 替代,但實測效能無差異TOP 關鍵字可有效控制資料量MAX/MIN 配合 >/< 運算子,使查詢符合 SARG 形式,啟用索引求值SELECT TOP 頁大小 *
FROM table1
WHERE id > (SELECT MAX(id) FROM (SELECT TOP ((頁碼-1)*頁大小) id FROM table1 ORDER BY id) AS T)
ORDER BY id
| 頁碼 | 方案1(游標) | 方案2(NOT IN) | 方案3(MAX/MIN) |
|---|---|---|---|
| 1 | 60 | 30 | 76 |
| 10 | 46 | 16 | 63 |
| 100 | 1076 | 720 | 130 |
| 500 | 540 | 12943 | 83 |
| 1000 | 17110 | 470 | 250 |
| 10000 | 24796 | 4500 | 140 |
| 100000 | 38326 | 42283 | 1553 |
| 250000 | 28140 | 128720 | 2330 |
| 500000 | 121686 | 127846 | 7168 |
CREATE PROCEDURE pagination3
@tblName VARCHAR(255), -- 表名
@strGetFields VARCHAR(1000) = '*', -- 回傳欄位
@fldName VARCHAR(255) = '', -- 排序欄位
@PageSize INT = 10, -- 頁大小
@PageIndex INT = 1, -- 頁碼
@doCount BIT = 0, -- 是否回傳總筆數
@OrderType BIT = 0, -- 排序類型 0升序/1降序
@strWhere VARCHAR(1500) = '' -- 查詢條件(勿加 WHERE)
AS
BEGIN
DECLARE @strSQL VARCHAR(5000), @strTmp VARCHAR(110), @strOrder VARCHAR(400)
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 + "]"
END
ELSE
BEGIN
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
IF @PageIndex = 1
BEGIN
IF @strWhere != ''
SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields +
" FROM [" + @tblName + "] WHERE " + @strWhere + " " + @strOrder
ELSE
SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields +
" FROM [" + @tblName + "] " + @strOrder
END
ELSE
BEGIN
SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields +
" FROM [" + @tblName + "] WHERE [" + @fldName + "]" + @strTmp +
"([" + @fldName + "]) FROM (SELECT TOP " +
STR((@PageIndex-1)*@PageSize) + " [" + @fldName + "] FROM [" +
@tblName + "]" + @strOrder + ") AS tblTmp)" + @strOrder
IF @strWhere != ''
SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields +
" FROM [" + @tblName + "] WHERE [" + @fldName + "]" + @strTmp +
"([" + @fldName + "]) FROM (SELECT TOP " +
STR((@PageIndex-1)*@PageSize) + " [" + @fldName + "] FROM [" +
@tblName + "] WHERE " + @strWhere + " " + @strOrder + ") AS tblTmp) " +
"AND " + @strWhere + " " + @strOrder
END
END
EXEC(@strSQL)
END