【问题标题】:SQL Server 2008 paging methods?SQL Server 2008 分页方法?
【发布时间】:2011-05-20 11:38:18
【问题描述】:

我必须处理可能很大的记录列表,我一直在谷歌搜索避免选择整个列表的方法,而是让用户选择一个页面(比如从 1 到 10)并相应地显示记录.

比如说,对于 1000 条记录,我将有 100 页,每页 10 条记录,最近的 10 条记录将首先显示,然后如果用户单击第 5 页,它将显示从 41 到 50 的记录。

为每条记录添加一个行号然后根据行号进行查询是个好主意吗?有没有更好的方法可以在没有太多开销的情况下实现分页结果? 到目前为止,这里描述的那些方法看起来最有希望:

http://developer.berlios.de/docman/display_doc.php?docid=739&group_id=2899

http://www.codeproject.com/KB/aspnet/PagingLarge.aspx

【问题讨论】:

    标签: sql sql-server performance pagination


    【解决方案1】:

    试试这样的:

    declare @page int = 2
    declare @size int = 10
    
    declare @lower int =  (@page - 1) * @size
    declare @upper int =  (@page    ) * @size
    
    select * from (
    select 
        ROW_NUMBER() over (order by some_column) lfd,
    * from your_table
    ) as t
     where lfd between @lower and @upper
     order by some_column
    

    【讨论】:

    • ROW_NUMBER 已知在处理非常大的结果集时存在性能问题:4guysfromrolla.com/webtech/042606-1.shtml
    • 不应该是'declare @lower int = (@page * @size) - (@size - 1)'
    • @RoadWarrior:有趣的链接。我已将它添加到我最近的blog post 中,以提高分页速度。你知道seek method吗?它不允许真正的索引偏移,但可以在恒定时间内跳转到“下一页”。
    • 在测试中,@ReidEvans 是正确的,应该是 (@page * @size) - (@size - 1)(@page - 1) * @size 返回第 1-10 行,然后返回 10-20 而不是预期的 11-20。
    【解决方案2】:

    下面的 T-SQL 存储过程是一个非常高效的分页实现。 SQL 优化器可以非常快速地找到第一个 ID。将此与 ROWCOUNT 的使用结合起来,您就有了一种既能提高 CPU 效率又能提高读取效率的方法。对于具有大量行的表,它肯定优于我见过的使用临时表或表变量的任何方法。

    注意:我在此示例中使用了顺序标识列,但代码适用于任何适合页面排序的列。此外,正在使用的列中的序列中断不会影响结果,因为代码选择了许多行而不是列值。

    编辑:如果您正在对具有潜在非唯一值(例如 LastName)的列进行排序,则将第二列添加到 Order By 子句以使排序值再次唯一。

    CREATE  PROCEDURE dbo.PagingTest
    (
        @PageNumber int,
        @PageSize int
    )
    AS
    
    DECLARE @FirstId int, @FirstRow int
    
    SET @FirstRow = ( (@PageNumber - 1) * @PageSize ) + 1
    SET ROWCOUNT @FirstRow
    
    -- Add check here to ensure that @FirstRow is not
    -- greater than the number of rows in the table.
    
    SELECT   @FirstId = [Id]
    FROM     dbo.TestTable
    ORDER BY [Id]
    
    SET ROWCOUNT @PageSize
    
    SELECT   *
    FROM     dbo.TestTable
    WHERE    [Id] >= @FirstId
    ORDER BY [Id]
    
    SET ROWCOUNT 0
    GO 
    

    【讨论】:

    • @ScottE:id 中断对这段代码没有影响,因为上面的大小是页面中的行数(不是 id 数)。
    • @ScottE:另外,使用的列不需要是增量 id。您可以使用任何适合排序的列。
    • @Martin:同意,TOP 应该与 2000 年以后的任何 SQL Server 版本一样工作。我没有测试过,所以我只使用我测试过的代码。跨度>
    • 想想吧。这仍将最终进行 2 次不同的索引扫描,我认为这会比仅使用 row_count 并在一次扫描中完成所有操作效率稍低,因为它必须将索引根和中间页面导航到“在执行WHERE [Id] >= @FirstId bit 时找到它的位置”。那是假设两个扫描都将在同一个索引上。我想如果第一次扫描仅针对 id 上的极窄索引,第二次针对以 id 作为前导字段的覆盖索引,这可能会表现得更好!
    • @David:SQL Server int。你觉得 2^31-1 不够大?
    【解决方案3】:

    如果您使用具有两个 row_number() 列的 CTE - 一个按 asc 排序,一个按 desc 排序,您可以通过添加两个 row_number 列来获得用于分页的行号以及总记录。

    create procedure get_pages(@page_number int, @page_length int)
    as
        set nocount on;
    
        with cte as
        (
            select 
                Row_Number() over (order by sort_column desc) as row_num
                ,Row_Number() over (order by sort_column) as inverse_row_num
                ,id as cte_id
            From my_table
        )
        Select 
            row_num+inverse_row_num as total_rows
            ,*  
        from CTE inner join my_table
            on cte_id=df_messages.id
        where row_num between 
            (@page_number)*@page_length 
            and (@page_number+1)*@page_length
        order by rownumber
    

    【讨论】:

    • 关于逆向排序的说明,如果该列有非唯一数据,那么您有可能在 OVER 子句中的排序不一致。
    • 另外,您可以使用 ", Count(*) OVER() as total_row_count" 将一遍又一遍地返回相同数字的列。
    【解决方案4】:

    试试这个

    Declare @RowStart int, @RowEnd int;
    
    
    SET @RowStart = 4;
    SET @RowEnd = 7; 
    
    With MessageEntities As 
    (
        Select ROW_NUMBER() Over (Order By [MESSAGE_ID]) As Row, [MESSAGE_ID]
        From [TBL_NAFETHAH_MESSAGES]
    )
    Select  m0.MESSAGE_ID, m0.MESSAGE_SENDER_NAME,
            m0.MESSAGE_SUBJECT, m0.MESSAGE_TEXT
    From MessageEntities M
        Inner Join [TBL_NAFETHAH_MESSAGES] m0 on M.MESSAGE_ID = m0.MESSAGE_ID
    Where M.Row Between @RowStart AND @RowEnd
    Order By M.Row Asc
    GO
    

    【讨论】:

      【解决方案5】:

      这是@RoadWarrior 代码的更新版本,使用 TOP。性能是相同的,而且非常快。确保您在 TestTable.ID 上有一个索引

      CREATE PROC dbo.PagingTest
          @SkipRows int,
          @GetRows int
      AS
      DECLARE @FirstId int
      
      SELECT   TOP (@SkipRows) 
               @FirstId = [Id]
      FROM     dbo.TestTable
      ORDER BY [Id]
      
      SELECT   TOP (@GetRows) *
      FROM     dbo.TestTable
      WHERE    [Id] >= @FirstId
      ORDER BY [Id]
      
      GO 
      

      【讨论】:

        【解决方案6】:

        使用偏移量

        其他人已经解释了如何使用ROW_NUMBER() OVER() 排名功能来执行页面。值得一提的是,SQL Server 2012 终于包含了对 SQL 标准 OFFSET .. FETCH 子句的支持:

        SELECT first_name, last_name, score
        FROM players
        ORDER BY score DESC
        OFFSET 40 ROWS FETCH NEXT 10 ROWS ONLY
        

        如果您使用的是 SQL Server 2012 并且向后兼容性不是问题,您可能应该更喜欢这个子句,因为在极端情况下 SQL Server 会更优化地执行它。

        使用 SEEK 方法

        有一种完全不同的、更快的方式在 SQL 中执行分页。这通常被称为this blog post here 中描述的“seek 方法”。

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

        @previousScore@previousPlayerId 值分别是上一页最后一条记录的值。这允许您获取“下一页”。如果ORDER BY 方向是ASC,只需使用&gt; 代替。

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

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

        注意,“seek 方法”也称为keyset paging

        【讨论】:

        • 虽然可以很好地证明跳转到页面搜索 UI 无论如何都不是一个很好的设计,但我认为您确实需要向您的用户展示他们的搜索返回了多少结果(或在至少是一个近似值,就像谷歌所做的那样,尽管我不确定任何 SQL 数据库是否具有这种功能)。所以这种方法需要额外的计数查询,这大大增加了它的成本。
        • 这可能是 YMMV 的事情,但我在这个 seek 方法(在 sql server 2008 上,唉)和 sqlservercentral 文章中的“圣杯”方法进行了比较,它确实给了你计数,两者的表现非常相似。然而,将 count 查询的成本添加到 seek 方法使得 grail 查询成为明显的赢家。
        【解决方案7】:

        为什么不使用推荐的solution

        选择价值产品 FROM AdventureWorksEntities.Products 作为产品 按产品订购。ListPrice SKIP @skip LIMIT @limit

        【讨论】:

          猜你喜欢
          • 2015-09-29
          • 2011-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多