使用row_number从查询中获取@@ rowcount的有效方法

时间:2009-06-24 13:49:39

标签: sql sql-server sql-server-2005

我使用SQL Server 2005中的row_number over()功能进行了一次昂贵的查询。在查询分页时,我只返回这些记录的子列表。但是,我还想返回记录总数,而不仅仅是分页子集。有效地运行查询两次以获得计数是不可能的。

选择计数(*)也是不可能的,因为当我尝试这个时,表现非常糟糕。

我真正喜欢的是@@ ROW_NUMBERROWCOUNT: - )

5 个答案:

答案 0 :(得分:36)

与OVER(PARTITION BY ..)一起使用时检查COUNT(*)聚合,如下所示:

    SELECT
     ROW_NUMBER() OVER(ORDER BY object_id, column_id) as RowNum
    , COUNT(*) OVER(PARTITION BY 1) as TotalRows
    , * 
    FROM master.sys.columns

这是恕我直言,这是最好的方式,而无需进行两次查询。

答案 1 :(得分:36)

多年来,一堆开发人员的汗水已经有效地分页结果集。然而,没有一个答案 - 这取决于你的用例。部分用例是有效地获取页面,部分是确定完整结果集中有多少行。很抱歉,如果我稍微偏离分页,但两者在我的脑海里紧紧相连。

有很多策略,如果您有任何类型的数据量,大多数都是不好的。不适合用例。虽然这不是一个完整的清单,但以下是一些选项......

运行单独的Count(*)

  • 运行一个单独的查询,该查询执行一个简单的“从MyTable中选择计数(*)”
  • 简单易用的小桌子
  • 很好的未经过滤的大表,可以使用<或li>,也可以使用紧凑的非聚集索引
  • 如果您有一个复杂的WHERE/JOIN条件,则
  • 会发生故障,因为运行WHERE/JOIN两次是非常昂贵的。
  • 分解广泛的索引,因为读取次数增加。

合并ROW_Number() OVER()COUNT(1) OVER(PARTITION By 1)

  • 这是由@RBarryYoung提出的。它具有简单的优点 实施并且非常灵活。
  • 不利的一面是,有很多原因导致这种情况变得非常昂贵。
  • 例如,在我正在工作的数据库中,有一个大约6000行的媒体表。它不是特别宽,具有整数聚类PK,以及紧凑的唯一索引。然而,简单的COUNT(*) OVER(PARTITION BY 1) as TotalRows导致大约12,000次读取。将其与简单的SELECT COUNT(*) FROM Media - 12个读数进行比较。 Wowzers。
  

更新 - 我提到的读取问题有点红鲱鱼。事实证明,对于窗口函数,用于测量读取的单位是混合的。最终结果似乎是大量的读取。您可以在此处查看有关此问题的更多信息:Why are logical reads for windowed aggregate functions so high?

临时表/表变量

  • 有许多策略采用结果集并将相关键或结果段插入临时表/表变量。
  • 对于中小型结果集,这可以提供很好的结果。
  • 此类策略几乎适用于任何SQL平台/版本。
  • 对结果集进行多次操作(通常是一项要求)也很容易。
  • 缺点是处理大型结果集时...将几百万行插入临时表会产生成本。
  • 使问题更加复杂,在TempDB上的大量系统压力可能是一个很重要的因素,临时表在TempDB中有效工作。

高斯和/双行数

  • 这个想法依赖于数学家高斯想出的东西的子集(如何对一系列数字求和)。该子集是如何从表中的任何一点获取行计数。
  • 从一系列数字(Row_Number())开始,1到N的行数为(N + 1) - 1。链接中有更多解释。
  • 这个公式看起来好像只能用N,但如果你坚持使用公式,就会发生一些有趣的事情,你可以从表格中间的页面中找出行数。
  • 最终结果是ROW_Number() OVER(Order by ID)ROW_Number() OVER(Order by ID DESC)然后将两个数字相加并减去1.
  • 以我的媒体表为例,我的读数从12,000降至约75。
  • 在较大的页面中,您最终会多次重复数据,但读取中的偏移可能是值得的。
  • 我没有在太多情况下对此进行测试,因此在其他情况下它可能会崩溃。

顶部(@n)/ SET ROWCOUNT

  • 这些不是特定的策略本身,而是基于我们对查询优化器的了解进行优化。
  • 创造性地使用Top(@n)[top可以是SQL 2008中的变量]或SET ROWCOUNT可以减少您的工作集...即使您拉动结果集的中间页面,您仍然可以缩小结果范围
  • 这些想法因查询优化器行为而起作用...服务包/修补程序可以改变行为(尽管可能不会)。
  • 在certian实例中,SET ROWCOUNT可能有点准确
  • 此策略不考虑获取完整行数,只是使分页更有效

那么开发人员应该做什么?

读我的好人,读。以下是我倾斜的一些文章......

希望有所帮助。

答案 2 :(得分:4)

如果count(*)很慢,您首先需要仔细检查索引并确保统计信息是最新的,从而解决该问题。

根据我的经验,没有比做两个单独的查询更好的了,一个用于获取数据页,另一个用于获取总计数。随着行数的增加,使用临时表来获取总计数是一种失败的策略。例如,将一万亿行插入临时表只是为了计算它们的成本显然会过高。

答案 3 :(得分:0)

我这样做是将整个结果集与row_number一起放入临时表中,然后使用@@ rowcount并使用该查询返回我需要的数据页面。

答案 4 :(得分:0)

在 SQL2016 中,您有 session_context - 分页和最大行数变得快速而简单。 因此,我自己的发明用于具有数百万行的表。:-

create function dbo.x (
@tr int
)
RETURNS int
AS
BEGIN
    declare @TotR INT
    if @tr = -1 begin
        select @TotR = cast(session_context(N'TotRows') as int)
    end
    else begin
        EXEC sp_set_session_context N'TotRows', @tr
        select @TotR = @tr
    end
return @TotR
end
go


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

EXEC sp_set_session_context N'TotRows', 0

;WITH Data_CTE 
AS
(
    SELECT [name], object_id
    FROM sys.all_objects
    --where name ='x1'
), 
Count_CTE 
AS 
(
    Select dbo.x((SELECT COUNT(*) AS TotalRows FROM Data_CTE)) x1
)
SELECT Data_CTE.*
FROM Data_CTE
cross join Count_CTE 
where Count_CTE.x1>0
ORDER BY [name]
OFFSET (@PageNum - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;

select dbo.x(-1)