SQL Server 2008的分页方法?

fre*_*red 10 sql sql-server performance pagination

我必须处理一个可能很大的记录列表,我一直在谷歌搜索避免选择整个列表的方法,而是我想让用户选择一个页面(如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

Roa*_*ior 15

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

  • +1为什么要使用`SET ROWCOUNT`而不是`TOP`? (2认同)
  • 考虑一下.这仍将最终进行2次不同的索引扫描,我认为这只会比使用`row_count`并且在一次扫描中完成所有操作效率稍低,因为它必须将索引根和中间页导航到"找到它的位置"当做'WHERE [Id]> = @FirstId bit`时.假设两个扫描都在同一个索引上.我想如果第一次扫描是针对id的极其狭窄的索引而第二次是针对覆盖索引而使用`id`作为领先字段,这可能会表现得更好! (2认同)

3Da*_*ave 7

如果您使用带有两个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
Run Code Online (Sandbox Code Playgroud)

  • 关于逆排序的注释,如果列具有非唯一数据,则您有可能在OVER子句中排序不一致. (2认同)

Luk*_*der 5

使用OFFSET

其他人已经解释了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
Run Code Online (Sandbox Code Playgroud)

如果您正在使用SQL Server 2012并且向后兼容性不是问题,那么您应该更喜欢这个子句,因为SQL Server在极端情况下会更好地执行它.

使用SEEK方法

在SQL中执行分页有一种完全不同的,更快的方法.这通常被称为"搜索方法",如本博客文章中所述.

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

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

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

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

注意,"搜索方法"也称为键集寻呼.


ber*_*d_k 4

尝试这样的事情:

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

  • 不应该是“声明@lower int = (@page * @size) - (@size - 1)” (3认同)
  • 已知 ROW_NUMBER 在处理非常大的结果集时存在性能问题:http://www.4guysfromrolla.com/webtech/042606-1.shtml (2认同)