具有分页和计数的SQL Server查询

时间:2014-02-03 20:52:05

标签: sql-server pagination common-table-expression

我想用分页进行数据库查询。所以,我使用了一个通用表表达式和一个排名函数来实现这一点。请看下面的例子。

declare @table table (name varchar(30));
insert into @table values ('Jeanna Hackman');
insert into @table values ('Han Fackler');
insert into @table values ('Tiera Wetherbee');
insert into @table values ('Hilario Mccray');
insert into @table values ('Mariela Edinger');
insert into @table values ('Darla Tremble');
insert into @table values ('Mammie Cicero');
insert into @table values ('Raisa Harbour');
insert into @table values ('Nicholas Blass');
insert into @table values ('Heather Hayashi');

declare @pagenumber int = 2;
declare @pagesize int = 3;
declare @total int;

with query as
(
    select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from @table
)
select top (@pagesize) name from query
    where line > (@pagenumber - 1) * @pagesize

在这里,我可以指定@pagesize和@pagenumber变量来为我提供我想要的记录。但是,此示例(来自存储过程)用于在Web应用程序中进行网格分页。此Web应用程序需要显示页码。例如,如果数据库中有12条记录且页面大小为3,那么我将需要显示4个链接,每个链接代表一个页面。

但是如果不知道有多少记录我就不能这样做,这个例子只给了我记录的子集。

然后我更改了存储过程以返回计数(*)。

declare @pagenumber int = 2;
declare @pagesize int = 3;
declare @total int;
with query as
(
    select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line, total = count(*) over()from @table
)
select top (@pagesize) name, total from query
    where line > (@pagenumber - 1) * @pagesize

因此,与每一行一起,它将显示记录总数。但我不喜欢它。

我的问题是,如果有更好的方法(性能),可以设置@total变量而不在SELECT中返回此信息。或者这个总列是否会对性能造成太大影响?

由于

5 个答案:

答案 0 :(得分:46)

假设您使用的是MSSQL 2012,则可以使用Offset and Fetch来大大清理服务器端分页。我们发现性能很好,而且在大多数情况下更好。获取总列数,只需使用内联下面的窗口函数...它不包括'offset'和'fetch'施加的限制。

对于Row_Number,您可以按照您的方式使用窗口函数,但我建议您将该客户端计算为(pagenumber * pagesize + resultsetRowNumber),因此如果您在10个结果的第5页并且在第三行你会输出第53行。

当应用于包含约200万个订单的订单表时,我发现了以下内容:

快速版

这是在一秒钟之内。关于它的好处是你可以在公共表表达式中进行一次过滤,它既适用于分页过程又适用于计数。当where子句中有许多谓词时,这会使事情变得简单。

declare @skipRows int = 25,
        @takeRows int = 100,
        @count int = 0

;WITH Orders_cte AS (
    SELECT OrderID
    FROM dbo.Orders
)

SELECT 
    OrderID,
    tCountOrders.CountOrders AS TotalRows
FROM Orders_cte
    CROSS JOIN (SELECT Count(*) AS CountOrders FROM Orders_cte) AS tCountOrders
ORDER BY OrderID
OFFSET @skipRows ROWS
FETCH NEXT @takeRows ROWS ONLY;

慢速版

这花费了大约10秒,而计数(*)导致了缓慢。我很惊讶这是如此之慢,但我怀疑它只是计算每一行的总数。虽然这很干净。

declare @skipRows int = 25,
@takeRows int = 100,
@count int = 0


SELECT 
    OrderID,
    Count(*) Over() AS TotalRows
FROM Location.Orders
ORDER BY OrderID
OFFSET @skipRows ROWS
FETCH NEXT @takeRows ROWS ONLY;

<强>结论

我们之前已经完成了这个性能调优过程,并且实际上发现它依赖于查询,使用的谓词和涉及的索引。例如,第二个我们引入了一个视图,因此我们实际查询基表,然后连接视图(包括基表),它实际上表现非常好。

我建议采用一些直接的策略,并将它们应用于高价值的查询。

答案 1 :(得分:4)

DECLARE @pageNumber INT = 1  , 
        @RowsPerPage INT = 20

SELECT  *
FROM    TableName
ORDER BY Id
        OFFSET ( ( @pageNumber - 1 ) * @RowsPerPage ) ROWS
             FETCH NEXT @RowsPerPage ROWS ONLY;

答案 2 :(得分:1)

如果事先算出计算怎么办?

declare @pagenumber int = 2;
declare @pagesize int = 3;
declare @total int;

SELECT @total = count(*)
FROM @table

with query as
(
   select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from @table
)
select top (@pagesize) name, @total total from query
where line > (@pagenumber - 1) * @pagesize

另一种方法是计算max(line)。检查链接

Return total records from SQL Server when using ROW_NUMBER

UPD:

对于单个查询,请在上面的链接上查看marc_s的答案。

    with query as
    (
       select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from @table
    )
    select top (@pagesize) name, 
       (SELECT MAX(line) FROM query) AS total 
    from query
    where line > (@pagenumber - 1) * @pagesize

答案 3 :(得分:1)

@pagenumber=5
@pagesize=5

创建一个公用表表达式并像这样编写逻辑

Between ((@pagenumber-1)*(@pagesize))+1 and (@pagenumber *@pagesize)

答案 4 :(得分:0)

我们可以通过多种方式实现分页:我希望这些信息对您和其他人有用。

示例1:使用offset-fetch next子句。 2005年推出

declare @table table (name varchar(30));
insert into @table values ('Jeanna Hackman');
insert into @table values ('Han Fackler');
insert into @table values ('Tiera Wetherbee');
insert into @table values ('Hilario Mccray');
insert into @table values ('Mariela Edinger');
insert into @table values ('Darla Tremble');
insert into @table values ('Mammie Cicero');
insert into @table values ('Raisa Harbour');
insert into @table values ('Nicholas Blass');
insert into @table values ('Heather Hayashi');

declare @pagenumber int = 1
declare @pagesize int = 3

--this is a CTE( common table expression and this is introduce in 2005)
with query as
(
  select ROW_NUMBER() OVER(ORDER BY name ASC) as line, name from @table
) 

--order by clause is required to use offset-fetch
select * from query
order by name 
offset ((@pagenumber - 1) * @pagesize) rows
fetch next @pagesize rows only

示例2:使用row_number()函数和

之间
declare @table table (name varchar(30));
insert into @table values ('Jeanna Hackman');
insert into @table values ('Han Fackler');
insert into @table values ('Tiera Wetherbee');
insert into @table values ('Hilario Mccray');
insert into @table values ('Mariela Edinger');
insert into @table values ('Darla Tremble');
insert into @table values ('Mammie Cicero');
insert into @table values ('Raisa Harbour');
insert into @table values ('Nicholas Blass');
insert into @table values ('Heather Hayashi');

declare @pagenumber int = 2
declare @pagesize int = 3

SELECT *
FROM 
(select ROW_NUMBER() OVER (ORDER BY PRODUCTNAME) AS RowNum, * from Products)
as Prodcut
where RowNum between (((@pagenumber - 1) * @pageSize )+ 1) 
and (@pagenumber * @pageSize )

我希望这些对所有人都有帮助