从 OFFSET / FETCH NEXT 获取总行数
Posted
技术标签:
【中文标题】从 OFFSET / FETCH NEXT 获取总行数【英文标题】:Getting total row count from OFFSET / FETCH NEXT 【发布时间】:2012-09-03 08:46:50 【问题描述】:所以,我有一个函数可以返回一些我想在我的网站上实现分页的记录。有人建议我使用 SQL Server 2012 中的 Offset/Fetch Next 来完成此操作。在我们的网站上,我们有一个区域列出了记录总数以及您当时所在的页面。
以前,我获得了整个记录集,并且能够以编程方式在其上构建分页。但是使用带有 FETCH NEXT X ROWS ONLY 的 SQL 方式,我只返回 X 行,所以我不知道我的总记录集是多少以及如何计算我的最小和最大页面。我可以告诉这样做的唯一方法是调用该函数两次并在第一个上计算行数,然后使用 FETCH NEXT 运行第二个。有没有更好的方法不会让我运行两次查询?我正在努力提高性能,而不是减慢它。
【问题讨论】:
【参考方案1】:我在使用 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
【讨论】:
如果有可能将 COUNT(*) 值保存到变量中,那就太棒了。我可以将其设置为我的存储过程的输出参数。有什么想法吗? 有没有办法在单独的表中获取计数?看来您只能对前面的第一个 SELECT 语句使用“TempResult”。 为什么效果这么好?在第一个 CTE 中,所有行都被选中,然后通过 fetch 缩减。我猜想在第一个 CTE 中选择所有行会显着减慢速度。无论如何,谢谢你! 在我的情况下,它比 COUNT(1) OVER() 慢。可能是因为选择中的一个函数。 这非常适合小型数据库,当行数为数百万时需要太多时间。【参考方案2】:你可以使用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;
但是,这应该保留给小数据集;在较大的集合上,性能可能很糟糕。 See this Paul White article for better alternatives,包括维护索引视图(仅当结果未过滤或您提前知道 WHERE
子句时才有效)和使用 ROW_NUMBER()
技巧。
【讨论】:
在有 3,500,000 条记录的表中,COUNT(*) OVER() 耗时 1 分 3 秒。下面由 James Moberg 描述的方法需要 13 秒来检索相同的数据集。我确信 Count Over 方法适用于较小的数据集,但是当您开始变得非常大时,它会大大减慢。 或者你可以只使用 COUNT(1) OVER() ,因为它不必像 count(*) 那样从表中读取实际数据 @AaronBertrand 真的吗?这一定意味着您要么拥有一个包含所有列的索引,要么自 2008R2 以来这已得到很大改进。在那个版本中,count(*) 是按顺序工作的,这意味着首先选择 *(如:所有列),然后计算。如果你做了一个 count(1),你只需要选择一个常量,这比读取实际数据要快得多。 @idx 不,这也不是 2008 R2 中的工作方式,抱歉。我从 6.5 开始就一直在使用 SQL Server,我不记得有一次引擎不够智能,只能扫描 COUNT(*) 或 COUNT(1) 的最窄索引。当然不是自 2000 年以来。但是,嘿,我有一个 2008 R2 的实例,你能在 SQLfiddle 上设置一个重现你声称存在的这种差异吗?我很乐意尝试。 在 sql server 2016 数据库上,搜索包含约 2500 万行的表,分页约 3000 个结果(有多个连接,包括表值函数),这需要几毫秒 - 太棒了! 【参考方案3】:显然,根据查询的不同,结果可能会有很大差异。我用这些结果测试了我的案例:(8 个连接,2 个子查询,5800 行不同的结果,5900 行不不同):
~0.820 秒使用COUNT(1) OVER()
(Aaron Bertrand's answer,但结果错误*)
~0.850 秒使用#TEMP
表。
~1.590 秒 WITH .. AS
(James Moberg's anser)
~1.600 秒运行两次(第一次没有排序,只是为了计数)
*在我的情况下,Aaron Bertrand's 的答案没有奏效,因为COUNT(1) OVER()
似乎包括被DISTINCT
过滤掉的行。
使用临时表:
DECLARE
@PageSize INT = 10,
@PageNum INT = 1;
SELECT
name, object_id
INTO #MY_TEMP
FROM sys.all_objects
SELECT *
FROM #MY_TEMP
ORDER BY name
OFFSET (@PageNum-1)*@PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
SELECT COUNT(1) FROM #MY_TEMP
-- or
-- SELECT @MY_OUTPUT_PARAM = COUNT(1) FROM #MY_TEMP
DROP TABLE #MY_TEMP
临时表的好处是可以将计数分成不同的结果或输出参数。
【讨论】:
【参考方案4】:基于James Moberg's answer:
这是使用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
【讨论】:
以上是关于从 OFFSET / FETCH NEXT 获取总行数的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server 2012使用Offset/Fetch Next实现分页
SqlException: ‘OFFSET‘ 附近有语法错误。在 FETCH 语句中选项 NEXT 的用法无效。