【问题标题】:Select COUNT(*) of subquery without running it twice选择子查询的 COUNT(*) 而不运行两次
【发布时间】:2010-10-09 13:09:12
【问题描述】:

我有一个过程来返回一个受页码和其他东西限制的结果集。作为 OUTPUT 参数,我需要根据除页码以外的参数返回所选行的总数。所以我有类似的东西:

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position
FROM Items
WHERE Row2 = @Row2)
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

然后我需要将 OUTPUT 参数设置为内部查询中的行数。我可以只复制查询并对其进行计数,但是此查询可能会返回数千行(并且将来会更多),因此我正在寻找具有良好性能的方法。我在考虑表变量,这是个好主意吗?或者有什么其他建议?

更具体地说,它是 Microsoft SQL Server 2008。

谢谢你,简

【问题讨论】:

    标签: sql sql-server tsql count paging


    【解决方案1】:

    我认为您应该在单独的查询中执行此操作。虽然这两个查询看起来几乎相同,但查询优化器处理它们的方式会有很大不同。

    理论上,SQL Server 甚至可能不会遍历子查询中的所有行来计算它。

    【讨论】:

      【解决方案2】:

      我现在无法访问我的代码库,但我相信您可以使用 COUNT() OVER(或类似命令)将总行数作为子查询的一部分返回。然后,您可以将其作为最终结果集的一部分返回。它在每一行中都会重复,但在我看来,对于使用分页并且最终结果应该有限的应用程序来说,这对性能的影响很小。

      我会在几个小时后发布确切的代码。

      编辑:这是我用来生成计数的行。最后,我们的开发人员想要一个单独的方法来自己获取计数,所以现在我在同一个存储过程中的两个位置维护搜索条件。

      COUNT(*) OVER (PARTITION BY '') AS TotalCount
      

      将其添加到您的 CTE,然后您可以选择 TotalCount,它将成为您每一行中的一列。

      【讨论】:

        【解决方案3】:

        您可以使用 COUNT(*) 在主查询中将总行数计为单独的列。像这样:

        WITH SelectedItems AS
        (SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
        COUNT(*) OVER () AS TotalRows
        FROM Items
        WHERE Row2 = @Row2)
        SELECT Id, Row1, Row2
        FROM SelectedItems
        WHERE Position BETWEEN @From AND @To
        

        这将在您的结果集中而不是在输出参数中返回计数,但这应该符合您的要求。否则,结合临时表:

        DECLARE @tmp TABLE (Id int, RowNum int, TotalRows int);
        
        WITH SelectedItems AS
        (SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
        COUNT(*) OVER () AS TotalRows
        FROM Items
        WHERE Row2 = @Row2)
        INSERT @tmp
        SELECT Id, Row1, Row2
        FROM SelectedItems
        WHERE Position BETWEEN @From AND @To
        
        SELECT TOP 1 @TotalRows = TotalRows FROM @tmp
        SELECT * FROM @tmp
        

        您会发现,仅将临时表用于分页结果不会占用太多内存(当然取决于您的页面大小),而且您只能将其保留很短的时间。从临时表中选择完整的结果集并选择 TotalRows 只会花费一点时间。

        这将比运行完全独立的查询快得多,在我的测试中(重复 WITH)使执行时间加倍。

        【讨论】:

        • +1 for Count(*) over() 但不是 Count(1) over() 会更好吗?
        • 在性能方面没有区别,SQL 内部将 count() 更改为与 count(1) 相同。在 MySql 中也是如此,它将 count() 转换为 count(0)。虽然 count(1) 的输入速度更快!
        【解决方案4】:

        您不能将输出变量设置为@@RowCount 吗?这将获得受最后执行语句影响的行:

        SELECT stuff FROM mytable
        
        SET @output = @@ROWCOUNT
        

        这应该可以满足您的需求,并且不需要再次运行查询。

        【讨论】:

        • 恐怕不行,这将给出所选页面中的记录数,而不是集合中的总记录数。
        【解决方案5】:

        您必须在不限制范围的情况下运行整个查询,至少运行一次才能获得完整的行数。由于无论如何您都将这样做,因此您应该选择 @@RowCount 以输出找到的总行数,而不是在每行中使用冗余的 count(*) 列来重载数据读取器。

        1.第一次运行NEW查询时:

        select YOUR_COLUMNS 
        from YOUR_TABLE 
        where YOUR_SEARCH_CONDITION 
        order by YOUR_COLUMN_ORDERING_LIST;
        select @@rowcount;
        

        2。只读取前 X 行

        上述查询避免了使用冗余 COUNT(*) 列淹没 SqlDataReader,否则每次调用 SqlDataReader.Read() 时都会发送该列。 由于您是第一次运行查询...而不是选择范围,只需读取前 X 行。这正是您想要的...完整的结果计数,前 X 条记录,以及没有冗余计数列的结果集的高效流式传输。

        3。对于 SAME 查询的后续运行以获取结果的子集

        select YOUR_COLUMNS 
        from (select YOUR_COLUMNS, ROW_NUMBER() 
        over(order by BY YOUR_COLUMN_ORDERING_LIST) as RowNum) Results 
        where Results.RowNum between @From and @To;
        

        在任何情况下,@@rowcount 是在第一次运行查询时访问计数而不限制结果集的最直接方法(无论如何你都想要第 X 个结果),无需运行单独的 count() 查询,不使用临时表,也不包括多余的 count() 列。

        【讨论】:

          猜你喜欢
          • 2014-12-13
          • 1970-01-01
          • 2022-01-10
          • 1970-01-01
          • 1970-01-01
          • 2017-02-19
          • 2012-04-13
          • 1970-01-01
          • 2023-04-01
          相关资源
          最近更新 更多