在SQL Server 2000、2005、2008、2012中分页结果的最佳方法(性能方面)是什么?如果你还想获得结果的总数(在分页之前)?


当前回答

获取结果总数和分页是两个不同的操作。对于这个示例,让我们假设您正在处理的查询是

SELECT * FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate

在这种情况下,您将使用以下命令确定结果的总数:

SELECT COUNT(*) FROM Orders WHERE OrderDate >= '1980-01-01'

...这可能看起来效率很低,但实际上性能很好,假设所有索引等都正确设置。

接下来,要以分页方式返回实际结果,下面的查询将是最有效的:

SELECT  *
FROM    ( SELECT    ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
          FROM      Orders
          WHERE     OrderDate >= '1980-01-01'
        ) AS RowConstrainedResult
WHERE   RowNum >= 1
    AND RowNum < 20
ORDER BY RowNum

这将返回原始查询的第1-19行。这里很酷的事情是,尤其是对于web应用程序,你不需要保留任何状态,除了要返回的行号。

其他回答

MSDN: ROW_NUMBER (Transact-SQL)

返回结果集中分区内某一行的顺序编号,每个分区中的第一行从1开始。 下面的示例按照OrderDate的顺序返回数字为50到60的行。

WITH OrderedOrders AS
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY FirstName DESC) AS RowNumber, 
        FirstName, LastName, ROUND(SalesYTD,2,1) AS "Sales YTD"
    FROM [dbo].[vSalesPerson]
) 
SELECT RowNumber, 
    FirstName, LastName, Sales YTD 
FROM OrderedOrders 
WHERE RowNumber > 50 AND RowNumber < 60;
  RowNumber FirstName    LastName               SalesYTD
  --- -----------  ---------------------- -----------------
  1   Linda        Mitchell               4251368.54
  2   Jae          Pak                    4116871.22
  3   Michael      Blythe                 3763178.17
  4   Jillian      Carson                 3189418.36
  5   Ranjit       Varkey Chudukatil      3121616.32
  6   José         Saraiva                2604540.71
  7   Shu          Ito                    2458535.61
  8   Tsvi         Reiter                 2315185.61
  9   Rachel       Valdez                 1827066.71
  10  Tete         Mensa-Annan            1576562.19
  11  David        Campbell               1573012.93
  12  Garrett      Vargas                 1453719.46
  13  Lynn         Tsoflias               1421810.92
  14  Pamela       Ansman-Wolfe           1352577.13

最后,微软SQL Server 2012发布了,我真的很喜欢它的简单的分页,你不需要使用复杂的查询,就像这里回答的。

要获得接下来的10行,只需运行这个查询:

SELECT * FROM TableName ORDER BY id OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;

https://learn.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql#using-offset-and-fetch-to-limit-the-rows-returned

使用时需要考虑的要点:

ORDER BY是强制使用OFFSET…获取子句。 OFFSET子句在FETCH中是强制的。你不能使用ORDER BY… 取回。 在同一个查询中,TOP不能与OFFSET和FETCH组合使用 表达式。

试试这个方法:

SELECT TOP @offset a.*
FROM (select top @limit b.*, COUNT(*) OVER() totalrows 
        from TABLENAME b order by id asc) a
ORDER BY id desc;

对于SQL Server 2000,您可以使用一个带有IDENTITY列的表变量来模拟ROW_NUMBER():

DECLARE @pageNo int -- 1 based
DECLARE @pageSize int
SET @pageNo = 51
SET @pageSize = 20

DECLARE @firstRecord int
DECLARE @lastRecord int
SET @firstRecord = (@pageNo - 1) * @pageSize + 1 -- 1001
SET @lastRecord = @firstRecord + @pageSize - 1   -- 1020

DECLARE @orderedKeys TABLE (
  rownum int IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
  TableKey int NOT NULL
)

SET ROWCOUNT @lastRecord
INSERT INTO @orderedKeys (TableKey) SELECT ID FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate

SET ROWCOUNT 0

SELECT t.*
FROM Orders t
  INNER JOIN @orderedKeys o ON o.TableKey = t.ID
WHERE o.rownum >= @firstRecord
ORDER BY o.rownum

这种方法可以扩展到具有多列键的表,并且不会引起使用OR(跳过索引使用)的性能开销。缺点是,如果数据集非常大,并且其中一个接近最后一页,则会占用大量临时空间。在这种情况下,我没有测试游标的性能,但它可能会更好。

注意,这种方法可以针对数据的第一页进行优化。另外,使用ROWCOUNT是因为TOP在SQL Server 2000中不接受变量。

令人难以置信的是,没有其他答案提到在所有SQL Server版本中进行分页的最快方法。对于大页码,偏移量可能非常慢。在SQL中有一种完全不同的更快的分页方法。这通常被称为“查找方法”或“键集分页”,如本文所述。

SELECT TOP 10 first_name, last_name, score, COUNT(*) OVER()
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

"seek谓词"

@previousScore和@previousPlayerId值分别是前一页中最后一条记录的值。这允许你获取“下一页”。如果ORDER BY方向为ASC,只需使用>即可。

使用上述方法,如果不先获取之前的40条记录,就不能立即跳转到第4页。但通常情况下,你并不想跳得那么远。相反,您将获得一个更快的查询,可能能够在常数时间内获取数据,这取决于您的索引。另外,你的页面保持“稳定”,无论底层数据是否发生变化(例如,在第1页,而你在第4页)。

例如,当在web应用程序中延迟加载更多数据时,这是实现分页的最佳方法。

注意,“seek方法”也称为键集分页。

分页前的记录总数

COUNT(*) OVER()窗口函数将帮助您计算“分页前”的总记录数量。如果您使用的是SQL Server 2000,则必须对COUNT(*)进行两次查询。