从OFFSET / FETCH NEXT获取总行数


90

因此,我有一个函数,该函数返回一些要在我的网站上实现分页的记录。建议我使用SQL Server 2012中的Offset / Fetch Next来完成此任务。在我们的网站上,我们有一个区域,列出了记录总数以及您当时所在的页面。

以前,我获得了整个记录集,并能够以编程方式建立分页。但是,仅将SQL方法与FETCH NEXT X ROWS一起使用,我只得到X行,所以我不知道我的总记录集是什么以及如何计算我的最小和最大页面。我能做到的唯一方法是两次调用该函数,并在第一个函数上进行行计数,然后使用FETCH NEXT运行第二个函数。有没有一种更好的方法可以让我不再运行查询两次?我正在尝试提高性能,而不是降低性能。

Answers:


112

您可以使用COUNT(*) OVER()...这是使用sys.all_objects以下示例的快速示例:

DECLARE 
  @PageSize INT = 10, 
  @PageNum  INT = 1;

SELECT 
  name, object_id, 
  overall_count = COUNT(*) OVER()
FROM sys.all_objects
ORDER BY name
  OFFSET (@PageNum-1)*@PageSize ROWS
  FETCH NEXT @PageSize ROWS ONLY;

但是,这应该保留给较小的数据集。在较大的场景中,演奏可能会很糟糕。请参阅Paul White的这篇文章,以获得更好的选择,包括维护索引视图(仅在结果未经过滤或您WHERE事先知道子句的情况下才有效)以及使用ROW_NUMBER()技巧。


44
在具有3,500,000条记录的表中,COUNT(*)OVER()花费了1分3秒。James Moberg下面描述的方法花费了13秒来检索相同的数据集。我敢肯定,Count Over方法适用于较小的数据集,但是当您开始变得很大时,它会大大降低速度。
matthew_360

或者,您也可以使用COUNT(1)OVER(),这更快了,因为它不必从表中读取实际数据,就像count(*)一样
ldx

1
@AaronBertrand真的吗?这必须意味着您要么具有包含所有列的索引,要么自2008R2起已对其进行了很多改进。在该版本中,count(*)按顺序工作,这意味着首先选择*(如:所有列),然后进行计数。如果执行了count(1),则只需选择一个常数,该常数比读取实际数据快得多。
ldx

5
@idx不,那也不是2008 R2中的工作原理,对不起。自6.5以来,我一直在使用SQL Server,我不记得有一次引擎不够智能,无法仅扫描COUNT(*)或COUNT(1)的最窄索引。当然不是自2000年以来。但是,嘿,我有一个2008 R2实例,您可以在SQLfiddle上设置一个repro来证明您声称存在这种差异吗?我很乐意尝试。
亚伦·

2
在sql server 2016数据库上,在具有约2500万行的表上进行搜索,对约3000个结果进行分页(具有多个联接,包括对表值函数的联接),这花了毫秒-太棒了!
jkerak

138

使用COUNT()OVER()方法遇到一些性能问题(我不确定是否是服务器,因为花了40秒返回了10条记录,然后以后没有任何问题。)该技术在所有条件下都有效,而不必使用COUNT()OVER()并完成了一样:

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, TempCount
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY

31
如果有可能将COUNT(*)值保存到变量中,那将真的很棒。我可以将其设置为我的存储过程的OUTPUT参数。有任何想法吗?
到2014年

1
有什么方法可以在单独的表中获得计数吗?似乎您只能对前面的第一个SELECT语句使用“ TempResult”。
matthew_360 2014年

declare @result table ( ID int, Name nvarchar(max), counter int ) 那么在上述声明中,CTE选择的最终对象可能是: SELECT * into @result FROM TempResult, TempCount ORDER BY TempResult.Name OFFSET (@PageNum-1)*@PageSize ROWS FETCH NEXT @PageSize ROWS ONLY 如果您需要通过过程重新使用行数和数据,请执行以下操作
Nathan Teague

4
为什么这样运作良好?在第一个CTE中,选择所有行,然后通过提取对其进行缩减。我猜想在第一个CTE中选择所有行会大大减慢速度。无论如何,谢谢!
jbd

1
在我的情况下,它的速度比COUNT(1)OVER()慢。
Tiju John

1

根据James Moberg的回答

Row_Number()如果您没有SQL Server 2012并且不能使用OFFSET,则可以使用,这是一种替代方法

DECLARE 
    @PageNumEnd INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, NAME
    FROM Tabla
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)

select * 
from
(
    SELECT
     ROW_NUMBER() OVER ( ORDER BY PolizaId DESC) AS 'NumeroRenglon', 
     MaxRows, 
     ID,
     Name
    FROM TempResult, TempCount

)resultados
WHERE   NumeroRenglon >= @PageNum
    AND NumeroRenglon <= @PageNumEnd
ORDER BY NumeroRenglon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.