SQL Server 2005 引入視窗函數 ROW_NUMBER() OVER,提供比舊版 TOP ... NOT IN 或游標更優雅、效能更好的分頁解決方案。本文從基本語法到實戰分頁範例完整說明。
ROW_NUMBER() OVER (PARTITION BY 分群欄位 ORDER BY 排序欄位) AS RowNum
PARTITION BY:選擇性,依欄位分群,各群內部編號從 1 開始。ORDER BY:必要,決定編號順序。CREATE TABLE Employee (EmpID INT, DeptID INT, Salary DECIMAL(10,2));
INSERT INTO Employee VALUES
(1,10,5500), (2,10,4500), (3,20,1900), (4,20,4800),
(5,40,6500), (6,40,14500), (7,40,44500), (8,50,6500), (9,50,7500);
SELECT
EmpID, DeptID, Salary,
ROW_NUMBER() OVER (PARTITION BY DeptID ORDER BY Salary DESC) AS RankInDept
FROM Employee;
/*
EmpID DeptID Salary RankInDept
7 40 44500 1
6 40 14500 2
5 40 6500 3
...
*/
-- 將編號結果作為衍生表
WITH Numbered AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Salary DESC) AS RowNum
FROM Employee
)
SELECT * FROM Numbered WHERE RowNum BETWEEN 1 AND 5; -- 第 1 頁
SELECT * FROM Numbered WHERE RowNum BETWEEN 6 AND 10; -- 第 2 頁
DECLARE @PageSize INT = 20, @PageNum INT = 3;
WITH Numbered AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY SomeColumn) AS RowNum
FROM YourTable
WHERE SomeCondition = 1
)
SELECT * FROM Numbered
WHERE RowNum BETWEEN (@PageNum - 1) * @PageSize + 1 AND @PageNum * @PageSize
ORDER BY RowNum;
同樣原理,將聯查結果包在 CTE 中編號再分頁:
WITH Numbered AS (
SELECT
o.OrderID, c.CustomerName, o.OrderDate, o.TotalAmount,
ROW_NUMBER() OVER (ORDER BY o.OrderDate DESC) AS RowNum
FROM Orders o
INNER JOIN Customers c ON c.CustomerID = o.CustomerID
WHERE o.Status = 'Completed'
)
SELECT * FROM Numbered
WHERE RowNum BETWEEN 21 AND 40; -- 第 2 頁,每頁 20 筆
OVER (ORDER BY ...) 依賴排序,確保排序欄位有索引 (含覆蓋索引) 避免 Sort 運算子。ROW_NUMBER 需全量排序,改用 WHERE Key > @LastKey ORDER BY Key LIMIT @PageSize (SQL 2012+ OFFSET/FETCH)。| 版本 | 分頁語法 | 特點 |
|---|---|---|
| SQL 2000 | TOP + NOT IN / 暫存表 | 效能差、語法繁瑣 |
| SQL 2005/2008/R2 | ROW_NUMBER() OVER + CTE | 標準視窗函數、廣泛支援 |
| SQL 2012+ | OFFSET @Rows FETCH NEXT @Rows ROWS ONLY | ANSI 標準、語法簡潔、計畫穩定 |