CREATE DATABASE data_Test;
USE data_Test;
CREATE TABLE tb_TestTable (
id INT IDENTITY(1,1) PRIMARY KEY,
userName NVARCHAR(20) NOT NULL,
userPWD NVARCHAR(20) NOT NULL,
userEmail NVARCHAR(40) NULL
);
-- 插入 200 萬筆測試資料
SET IDENTITY_INSERT tb_TestTable ON;
DECLARE @count INT = 1;
WHILE @count <= 2000000 BEGIN
INSERT INTO tb_TestTable(id, userName, userPWD, userEmail)
VALUES(@count, 'admin', 'admin888', 'lli0077@yahoo.com.cn');
SET @count = @count + 1;
END
SET IDENTITY_INSERT tb_TestTable OFF;
CREATE PROCEDURE proc_paged_with_notin
@pageIndex INT, @pageSize INT
AS
BEGIN
DECLARE @sql NVARCHAR(500), @timediff DATETIME = GETDATE();
SET @sql = 'SELECT TOP ' + STR(@pageSize) + ' * FROM tb_TestTable
WHERE ID NOT IN (SELECT TOP ' + STR(@pageSize*@pageIndex) + ' id FROM tb_TestTable ORDER BY ID ASC)
ORDER BY ID';
EXEC(@sql);
SELECT DATEDIFF(ms, @timediff, GETDATE()) AS 耗時;
END
缺點:NOT IN 子查詢需掃描大量資料,隨頁碼增加效能急劇下降。
CREATE PROCEDURE proc_paged_with_selectMax
@pageIndex INT, @pageSize INT
AS
BEGIN
DECLARE @sql NVARCHAR(500), @timediff DATETIME = GETDATE();
SET @sql = 'SELECT TOP ' + STR(@pageSize) + ' * FROM tb_TestTable
WHERE ID > (SELECT MAX(id) FROM (SELECT TOP ' + STR(@pageSize*@pageIndex) + ' id FROM tb_TestTable ORDER BY ID) AS TempTable)
ORDER BY ID';
EXEC(@sql);
SELECT DATEDIFF(ms, @timediff, GETDATE()) AS 耗時;
END
優點:避免 NOT IN 全掃描,利用 MAX 定位邊界。
CREATE PROCEDURE proc_paged_with_Midvar
@pageIndex INT, @pageSize INT
AS
BEGIN
DECLARE @count INT = 0, @ID INT = 0, @timediff DATETIME = GETDATE(), @sql NVARCHAR(500);
SELECT @count = @count + 1, @ID = CASE WHEN @count <= @pageSize*@pageIndex THEN ID ELSE @ID END
FROM tb_TestTable ORDER BY ID;
SET @sql = 'SELECT TOP ' + STR(@pageSize) + ' * FROM tb_TestTable WHERE ID > ' + STR(@ID);
EXEC(@sql);
SELECT DATEDIFF(ms, @timediff, GETDATE()) AS 耗時;
END
原理:單次掃描取得第 N 頁起始 ID,避免子查詢重複排序。
CREATE PROCEDURE proc_paged_with_Rownumber
@pageIndex INT, @pageSize INT
AS
BEGIN
DECLARE @timediff DATETIME = GETDATE();
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER(ORDER BY ID ASC) AS IDRank FROM tb_TestTable
) AS IDWithRowNumber
WHERE IDRank > @pageSize*@pageIndex AND IDRank < @pageSize*(@pageIndex+1);
SELECT DATEDIFF(ms, @timediff, GETDATE()) AS 耗時;
END
優點:語法標準、可讀性強、支援複雜排序。缺點:需全表排序產生行號,大資料量記憶體壓力大。
CREATE PROCEDURE proc_CTE
@pageIndex INT, @pageSize INT
AS
BEGIN
DECLARE @ctestr NVARCHAR(400), @strSql NVARCHAR(400), @datediff DATETIME = GETDATE();
SET @ctestr = 'WITH Table_CTE AS (SELECT CEILING((ROW_NUMBER() OVER(ORDER BY ID ASC))/'
+ STR(@pageSize) + ') AS page_num, * FROM tb_TestTable)';
SET @strSql = @ctestr + ' SELECT * FROM Table_CTE WHERE page_num = ' + STR(@pageIndex);
EXEC sp_executesql @strSql;
SELECT DATEDIFF(ms, @datediff, GETDATE()) AS 耗時;
END
| 方法 | 適用版本 | 大資料量效能 | 複雜排序支援 | 維護性 | 推薦指數 |
|---|---|---|---|---|---|
| NOT IN | 所有 | ★☆☆☆☆ | 差 | 低 | 不推薦 |
| MAX(ID) | 所有 | ★★★☆☆ | 一般 | 中 | 備選 |
| 中間變數 | 所有 | ★★★★☆ | 僅支援單欄位遞增鍵 | 中 | 推薦(舊版) |
| ROW_NUMBER() | SQL 2005+ | ★★★★☆ | 優秀 | 高 | 首選 |
| CTE 分組 | SQL 2005+ | ★★★★☆ | 優秀 | 高 | 首選 |
SELECT * FROM tb_TestTable
ORDER BY ID
OFFSET (@pageIndex-1)*@pageSize ROWS
FETCH NEXT @pageSize ROWS ONLY;
優勢:語法簡潔、標準化、優化器支援最佳、無需動態 SQL 拼接。