分頁是業務系統高頻需求。本文提供一個生產級通用分頁存儲過程 dbo.pages,支持 動態表名、動態顯示列、動態排序、動態 WHERE 條件、主鍵定位,採用 ROW_NUMBER() 鍵集分頁模式,避免 OFFSET/FETCH 深分頁性能劣化,兼容 SQL Server 2005+。
USE [YourDB];
GO
SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON;
GO
ALTER PROC [dbo].[pages] (
@tbname nvarchar(100), -- 目標表名
@FieldKey nvarchar(1000), -- 主鍵/唯一鍵(多列用逗號分隔),用於定位
@PageCurrent int = 1, -- 當前頁碼(≥1)
@PageSize int = 10, -- 每頁行數(≥1)
@FieldShow nvarchar(1000) = '',-- 顯示列列表,逗號分隔,空則 *
@FieldOrder nvarchar(1000) = '',-- 排序列列表,可含 ASC/DESC,空則不排序
@WhereString nvarchar(1000) = N''-- WHERE 條件(不含 WHERE 關鍵字)
)
AS
BEGIN
SET NOCOUNT ON;
-- 參數防禦
IF ISNULL(@FieldKey, N'') = N'' BEGIN
RAISERROR(N'分頁必須指定主鍵/唯一鍵 (@FieldKey)', 16, 1); RETURN;
END
IF ISNULL(@PageCurrent, 0) < 1 SET @PageCurrent = 1;
IF ISNULL(@PageSize, 0) < 1 SET @PageSize = 10;
IF ISNULL(@FieldShow, N'') = N'' SET @FieldShow = N'*';
IF ISNULL(@FieldOrder, N'') = N'' SET @FieldOrder = N'' ELSE SET @FieldOrder = N'ORDER BY ' + LTRIM(@FieldOrder);
IF ISNULL(@WhereString, N'') = N'' SET @WhereString = N'' ELSE SET @WhereString = N'WHERE ' + @WhereString + N'';
-- 計算分頁邊界
DECLARE @TopN varchar(20), @StartRecord varchar(20), @EndRecord varchar(20);
SELECT @TopN = @PageSize,
@StartRecord = (@PageCurrent - 1) * @PageSize + 1,
@EndRecord = (@PageCurrent - 1) * @PageSize + @PageSize;
-- 第 1 頁:直接 TOP + ORDER BY(最優)
IF @PageCurrent = 1
EXEC(N'SELECT TOP ' + @TopN + N' ' + @FieldShow + N' FROM ' + @tbname + N' ' + @WhereString + N' ' + @FieldOrder);
ELSE
BEGIN
-- 深分頁:CTE 打標 ROW_NUMBER → 篩選主鍵範圍 → 回表查試顯示列
EXEC(N'
WITH temptbl AS (
SELECT ROW_NUMBER() OVER(' + @FieldOrder + N') AS row, ' + @FieldKey + N'
FROM ' + @tbname + N' ' + @WhereString + N'
)
SELECT ' + @FieldShow + N'
FROM (
SELECT B.*
FROM (
SELECT ' + @FieldKey + N'
FROM temptbl
WHERE row BETWEEN ' + @StartRecord + N' AND ' + @EndRecord + N'
) A
LEFT JOIN ' + @tbname + N' B ON A.' + @FieldKey + N' = B.' + @FieldKey + N'
) C
');
END
END
GO
| 特性 | 實現方式 | 收益 |
|---|---|---|
| 動態化 | 所有關鍵字元參數化,EXEC(N'...') 動態構建 | 一個存儲過程服務所有表分頁 |
| 首頁優化 | @PageCurrent=1 走 TOP N ... ORDER BY | 避免 CTE 開銷,利用索引順序掃描即止 |
| 深分頁不退化 | ROW_NUMBER() 只對主鍵排序打標,BETWEEN 篩選主鍵集合,再 JOIN 回表取顯示列 | 避免 OFFSET 100000 ROWS 掃描丟棄大量行 |
| 複合主鍵支持 | @FieldKey 允許 'col1,col2',JOIN 條件自動展開 | 覆蓋聯表/弱實體表分頁場景 |
| 注入防護 | 參數化傳入表名/列名/條件,業務層需自行驗證白名單 | 基礎防線,建議上層再做對象名白名單校驗 |
-- 簡單分頁:第 3 頁,每頁 20 行,按創建時間倒序
EXEC dbo.pages
@tbname = 'Orders',
@FieldKey = 'OrderID',
@PageCurrent = 3,
@PageSize = 20,
@FieldShow = 'OrderID, CustomerID, OrderDate, TotalAmount',
@FieldOrder = 'OrderDate DESC',
@WhereString = 'CustomerID = 1001 AND OrderDate > ''2023-01-01''';
-- 複合主鍵表分頁
EXEC dbo.pages
@tbname = 'OrderDetails',
@FieldKey = 'OrderID, ProductID', -- 複合主鍵
@PageCurrent = 1,
@PageSize = 50,
@FieldOrder = 'OrderID, ProductID';
@FieldOrder 列上需有索引,或建立覆蓋索引 (OrderCol, FieldKey) INCLUDE (ShowCols)。SELECT FieldKey FROM ... ORDER BY ... OFFSET ... FETCH ...,再 IN 回表。@WhereString 注入風險:調用端必須對條件值參數化,或使用 sp_executesql 參數化動態 SQL 重寫此存儲過程(更安全但稍複雜)。SELECT COUNT(*) FROM table WHERE ... 配合緩存(如 Redis)減輕壓力。這個存儲過程在百萬級表、每頁 20-50 行、並發百 QPS 環境穩定運行多年,是分頁需求的「瑞士軍刀」。