本文提供一個高復用性的通用分頁預存程序 prcPageResult,支援動態資料表、欄位、條件、排序、主鍵與頁面大小,採用「主鍵範圍過濾」策略,在百萬列資料下仍能維持穩定效能。另附記錄數統計預存程序 prcRowsCount。
CREATE PROCEDURE prcPageResult
@currPage INT = 1, -- 目標頁碼 (1-based)
@showColumn VARCHAR(2000) = '*', -- 輸出欄位清單
@tabName VARCHAR(2000), -- 來源資料表名
@strCondition VARCHAR(2000) = '', -- WHERE 條件(不含 WHERE 關鍵字)
@ascColumn VARCHAR(100) = '', -- 排序欄位
@bitOrderType BIT = 0, -- 0=ASC, 1=DESC
@pkColumn VARCHAR(50) = '', -- 主鍵欄位名(必填)
@pageSize INT = 20 -- 每頁筆數
AS
BEGIN
SET NOCOUNT ON;
DECLARE @strOrderType VARCHAR(1000), -- ORDER BY 子句
@strTemp VARCHAR(1000), -- 子查詢比較運算子
@strSql VARCHAR(4000); -- 最終動態 SQL
/* 組裝 ORDER BY 與子查詢比較邏輯 */
IF @bitOrderType = 1
BEGIN
SET @strOrderType = ' ORDER BY ' + @ascColumn + ' DESC';
SET @strTemp = ' < (SELECT MIN(' + @pkColumn + ') FROM (SELECT TOP ' + CAST((@currPage-1)*@pageSize AS VARCHAR) + ' ' + @pkColumn + ' FROM ' + @tabName + @strOrderType + ') AS TabTemp)';
END
ELSE
BEGIN
SET @strOrderType = ' ORDER BY ' + @ascColumn + ' ASC';
SET @strTemp = ' > (SELECT MAX(' + @pkColumn + ') FROM (SELECT TOP ' + CAST((@currPage-1)*@pageSize AS VARCHAR) + ' ' + @pkColumn + ' FROM ' + @tabName + @strOrderType + ') AS TabTemp)';
END
/* 組裝主查詢 */
IF @currPage = 1
BEGIN
SET @strSql = 'SELECT TOP ' + CAST(@pageSize AS VARCHAR) + ' ' + @showColumn + ' FROM ' + @tabName +
CASE WHEN @strCondition <> '' THEN ' WHERE ' + @strCondition ELSE '' END +
@strOrderType;
END
ELSE
BEGIN
SET @strSql = 'SELECT TOP ' + CAST(@pageSize AS VARCHAR) + ' ' + @showColumn + ' FROM ' + @tabName +
CASE WHEN @strCondition <> '' THEN ' WHERE ' + @strCondition + ' AND ' ELSE ' WHERE ' END +
@pkColumn + @strTemp + @strOrderType;
END
EXEC (@strSql);
END
GO
-- 查詢 TableName 第 1 頁,每頁 25 筆,依 CreateDate 降冪,主鍵 PkID EXEC prcPageResult 1, '*', 'TableName', '', 'CreateDate', 1, 'PkID', 25;
CREATE PROC prcRowsCount
@tabName VARCHAR(200),
@colName VARCHAR(200) = '*',
@condition VARCHAR(200) = ''
AS
BEGIN
DECLARE @strSql VARCHAR(255);
IF @condition = ''
SET @strSql = 'SELECT COUNT(' + @colName + ') FROM ' + @tabName;
ELSE
SET @strSql = 'SELECT COUNT(' + @colName + ') FROM ' + @tabName + ' WHERE ' + @condition;
EXEC (@strSql);
END
GO
| 技巧 | 效益 |
|---|---|
主鍵範圍過濾 (PK > MAX(...)) | 避免 OFFSET 掃描前 N 頁,深頁效能 O(1) 不隨頁碼遞增。 |
| 排序欄位有索引 | 消除 Sort 運算子,配合 TOP 早期截斷。 |
| 僅選取必要欄位 (@showColumn) | 減少 I/O、網路傳輸、記憶體授與。 |
| 參數化動態 SQL | 計畫重用,降低編譯開銷(注意:表名/欄位名無法參數化,需防注入)。 |
@tabName、@pkColumn、@ascColumn 僅含合法識別碼字元 [a-zA-Z0-9_$]。QUOTENAME() 包裹識別碼:SET @tabName = QUOTENAME(@tabName);。@strCondition 若來自使用者輸入,**嚴禁直接拼接**,改用參數化或白名單驗證。-- 參數化、可讀性高、計畫重用佳 DECLARE @PageSize INT = 20, @PageIndex INT = 3; SELECT col1, col2, col3 FROM dbo.MyTable WHERE status = 1 ORDER BY created_at DESC OFFSET (@PageIndex - 1) * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY;
OFFSET/FETCH**,原生支援、最佳化器友善、無動態 SQL 風險。LIMIT ... OFFSET ... 或 OFFSET ... LIMIT ...。