在SQL Server中分页结果的最佳方法是什么

Pan*_*ros 441 sql sql-server performance pagination

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

Õzb*_*bek 450

最后,Microsoft SQL Server 2012发布了,我真的很喜欢它的分页简单,你不必使用这里回答的复杂查询.

要获取接下来的10行,只需运行此查询:

SELECT * FROM TableName ORDER BY id OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

http://technet.microsoft.com/en-us/library/gg699618.aspx

使用时需要考虑的要点:

  • ORDER BY必须使用OFFSET和FETCH子句.
  • FETCH强制使用OFFSET子句.你永远不能使用,ORDER BY ... FETCH.
  • TOP不能与OFFSET和FETCH在同一查询表达式中组合使用.

  • 还在等待'LISTAGG()`/`GROUP_CONCAT()`. (10认同)
  • @Jon,链接的博客文章不具代表性,从某种意义上说,它通过查找id列的值来返回页面结果进行比较. (4认同)
  • 羞耻性是如此糟糕http://www.mssqlgirl.com/paging-function-performance-in-sql-server-2012.html (3认同)
  • @RichardMarskell-Drackir `FOR XML PATH ('')` 有很多问题。首先,它将 XML 控制字符替换为 XML 实体代码。希望你的数据中没有 `<`、`>` 或 `&`!其次,以这种方式使用的`FOR XML PATH ('')` 实际上是未公开的语法。您应该指定命名列或备用元素名称。既不做也不在文档中,这意味着行为不可靠。第三,我们越是接受破坏的`FOR XML PATH ('')` 语法,MS 实际提供*real* `LISTAGG() [ OVER() ]` 函数的可能性就越小。 (2认同)

mdb*_*mdb 442

获得结果总数和分页是两种不同的操作.为了这个例子,让我们假设您正在处理的查询是

SELECT * FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您将使用以下内容确定结果总数:

SELECT COUNT(*) FROM Orders WHERE OrderDate >= '1980-01-01'
Run Code Online (Sandbox Code Playgroud)

...假设所有索引等都已正确设置,这看起来效率低下,但实际上非常高效.

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

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
Run Code Online (Sandbox Code Playgroud)

这将返回原始查询的第1-19行.这里很酷的东西,特别是对于web应用程序,是你不必保留任何状态,除了要返回的行号.

  • 请注意,SQL Server 2000中不存在ROW_NUMBER() (36认同)
  • 微软为SQL 2012添加了一项新功能,使分页与MySQL类似.请点击此链接了解具体方法.这是一篇有趣的文章:http://dbadiaries.com/new-t-sql-features-in-sql-server-2012-offset-and-fetch (9认同)
  • 这会返回内部查询中的所有行,然后根据外部查询进行过滤吗?例如:内部查询返回100,000,外部查询返回20. (6认同)
  • @SoftwareGeek:将其视为返回流的子查询(内部查询),然后读取该流,直到满足外部WHERE子句.如何与行相关,完全取决于查询,但优化器通常在最小化该数量方面做得非常好.在这方面,使用SQL Server Management Studio中的图形执行计划查看器(使用查询/包括实际执行计划)是非常有教育意义的. (2认同)
  • 好吧,如果你在内部选择中被公开(如你有内部联接),你怎么使用不同因为RowNumber是不同的,它不起作用 (2认同)
  • 如果表包含大量记录(即 &gt; 3.000.000)并且我们正在执行“offset / rownum”接近 3.000.000 的查询,则此方法不能提供良好的性能(至少少于 1 秒)。我只是尝试使用一个包含 3.000.000 条记录的简单表“person(电子邮件 PK、名字、姓氏)”,但要获取最后一条记录,使用上述查询需要 6 秒。我使用的是sql server 2008。 (2认同)
  • @broadband - 如果您有 3,000,000 行并且想要翻页到最后,您需要重新考虑您的用例的合法性(我发现很难相信 *用户* 会坐在那里翻阅 3,000,000 行)。如果您实际上有一个合法不可避免的要求,允许用户翻页到最后(我怀疑),那么您不应该天真地实现分页,您应该有一个有效的光标解决方案(不是 sql 光标),需要一些某种短暂的位置状态持久性,而不是这里发布的解决 99% 案例的千篇一律的解决方案。 (2认同)

Luk*_*der 94

令人难以置信的是,没有其他答案提到在所有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
Run Code Online (Sandbox Code Playgroud)

"寻求谓词"

@previousScore@previousPlayerId值是来自前一页的最后一条记录的相应值.这允许您获取"下一页".如果ORDER BY方向是ASC,只需使用>.

使用上述方法,您无法在未先读取前40条记录的情况下立即跳转到第4页.但通常情况下,你不想跳得那么远.相反,您可以获得更快的查询,该查询可能能够在固定时间内获取数据,具体取决于您的索引.此外,无论基础数据是否发生变化,您的页面都将保持"稳定"状态(例如,在第4页上,当您在第4页时).

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

注意,"搜索方法"也称为键集分页.

分页前的总记录

COUNT(*) OVER()窗口功能将帮助你"分页之前"算的总记录数.如果您使用的是SQL Server 2000,则必须使用两个查询COUNT(*).

  • 我有三个问题的搜索方法.[1]用户无法跳转到页面.[2]它假定顺序键,即如果有人删除了3行,那么我得到一个7项而不是10页的页面.`RowNumber`每页给我一个10项.[3]它不适用于假设`pagenumber`和`pagesize`的现有网格. (18认同)
  • @Junto:键集分页并不适合所有情况.它绝对不适用于数据网格.但它非常适合无限滚动Facebook Feed页面等场景.如果在顶部添加新帖子无关紧要,则在您向下滚动时,您的后续Feed帖子会正确添加到底部.这个完美的用法示例......这样的事情会更加难以实现,只能使用数字来实现偏移限制/获取. (7认同)
  • 我不得不同意Junto.这种方法完全排除了一个客户端,该客户端具有相当标准的分页ui"Previous 1 2 3(4)5 6 Next",用户可以在其中跳过.根据我的经验,这不是一个优势...... (4认同)
  • @ user960567:在性能方面,无论您是使用SQL标准`OFFSET .. FETCH`还是使用之前的`ROW_NUMBER()`技巧实现偏移分页,键集分页总是会超过偏移分页. (2认同)
  • 键集分页文章[这里](http://use-the-index-luke.com/no-offset) (2认同)

小智 27

从SQL Server 2012开始,我们可以使用OFFSETFETCH NEXTClause来实现分页.

试试这个,对于SQL Server:

在SQL Server 2012中,ORDER BY子句中添加了一项新功能,用于查询集合数据的优化,使用T-SQL以及SQL Server中的整个执行计划编写任何人的数据分页,使工作更轻松.

在T-SQL脚本下面,使用与前一个示例中使用的逻辑相同的逻辑.

--CREATING A PAGING WITH OFFSET and FETCH clauses IN "SQL SERVER 2012"
DECLARE @PageNumber AS INT, @RowspPage AS INT
SET @PageNumber = 2
SET @RowspPage = 10 
SELECT ID_EXAMPLE, NM_EXAMPLE, DT_CREATE
FROM TB_EXAMPLE
ORDER BY ID_EXAMPLE
OFFSET ((@PageNumber - 1) * @RowspPage) ROWS
FETCH NEXT @RowspPage ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

TechNet:使用SQL Server分页查询

  • 本次试验中最准确的答案 (2认同)
  • @Vikrant 仅当您忽略所有运行低于 2012 版本的人时 (2认同)
  • 该问题还询问分页之前的总行数,该答案未回答该问题。 (2认同)

lig*_*t78 15

有关不同分页技术的概述,请访问http://www.codeproject.com/KB/aspnet/PagingLarge.aspx

我经常使用ROWCOUNT方法主要使用SQL Server 2000(也可以使用2005和2008,只测量性能与ROW_NUMBER相比),它快速闪电,但你需要确保排序的列(主要是) )独特的价值观.

  • 有趣的是,那篇文章并没有提到[seek方法](http://stackoverflow.com/a/19609938/521799),它能够在恒定时间内进行分页……还是一篇好文章 (2认同)

Din*_*ara 15

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;
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)


Tho*_*ias 5

对于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
Run Code Online (Sandbox Code Playgroud)

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

请注意,可以针对第一页数据优化此方法.此外,由于TOP不接受SQL Server 2000中的变量,因此使用了ROWCOUNT.