如何优化执行嵌套在 group-by 子句中的计数的 SQL 查询?

Posted

技术标签:

【中文标题】如何优化执行嵌套在 group-by 子句中的计数的 SQL 查询?【英文标题】:How can I optimize a SQL query that performs a count nested inside a group-by clause? 【发布时间】:2011-02-16 23:43:36 【问题描述】:

我有一个图表应用程序,它动态生成 SQL Server 查询来计算给定图表上每个系列的值。这通常工作得很好,但我遇到了生成查询非常慢的特殊情况。查询如下所示:

SELECT 
  [dateExpr] AS domainValue,
  (SELECT COUNT(*) FROM table1 WHERE [dateExpr]=[dateExpr(maintable)] AND column2='A') AS series1

FROM table1 maintable
GROUP BY [dateExpr]
ORDER BY domainValue

我已缩写 [dateExpr] 因为它是 CAST 和 DATEPART 函数的组合,可将日期时间字段转换为 'yyyy-MM-dd' 形式的字符串,以便我可以轻松地按日历日中的所有值分组.上面的查询返回这些 yyyy-MM-dd 值作为图表 x 轴的标签和数据系列“series1”中的值以显示在图表上。数据系列应该计算属于该日历日且在 [column2] 中也包含特定值的记录数。 "[dateExpr]=[dateExpr(maintable)]" 表达式如下所示:

CAST(DATEPART(YEAR,dateCol) AS VARCHAR)+'-'+CAST(DATEPART(MONTH,dateCol) AS VARCHAR) = 
CAST(DATEPART(YEAR,maintable.dateCol) AS VARCHAR)+'-'+CAST(DATEPART(MONTH,maintable.dateCol) AS VARCHAR)

为一天加上一个额外的术语(为空间起见,上面省略了)。那是查询缓慢的根源,但我不知道如何重写查询,以便更有效地返回相同的结果。我可以完全控制查询的生成,所以如果我能找到返回相同结果的更高效的 SQL,我可以适当地修改查询生成器。任何指针将不胜感激。

【问题讨论】:

你使用的是哪个sql server? 您是否可以控制查询的生成方式? 这在 2005 和 2008 SQL Server 上运行。 我确实可以控制查询的生成。如果我能找出一个返回相同值的更高效的 SQL 查询,我可以修改查询生成以生成更高效的查询。 【参考方案1】:

我还没有测试,但我认为可以通过以下方式完成:

SELECT 
  [dateExpr] AS domainValue,
  SUM (CASE WHEN  column2='A' THEN 1 ELSE 0 END) AS series1

FROM table1 maintable
GROUP BY [dateExpr]
ORDER BY domainValue

【讨论】:

啊,这很好用。非常感谢您的帮助。 @Dan - 应该注意的是,此解决方案将仅包括您的表中存在的日期值,并且可能会产生间隙。即,它不包括没有值的日期。【参考方案2】:

最快的方法是使用日历表。为下一个知道多少年的每个月创建一个带有条目的 sql 表。然后从该日历表中选择,加入表 1 中日期介于该月开始日期和结束日期之间的条目。然后,如果您的聚集索引在 table1 中的 dateCol 上,查询将运行得非常快。

编辑:示例查询。这假设存在一个包含两列 StartDate 和 EndDate 的月份表,其中 EndDate 是下个月第一天的午夜。月份表上的聚集索引应该在 StartDate

SELECT
    months.StartDate,
    COUNT(*) AS [Count]
FROM months
INNER JOIN table1
    ON table1.dateCol >= months.StartDate AND table1.dateCol < months.EndDate
GROUP BY months.StartDate;

【讨论】:

谢谢,我有一些涉及日期范围的更复杂的查询,这会有所帮助。【参考方案3】:
With Calendar As
    (
    Select DateAdd(d, DateDiff(d, 0, Min( dateCol ) ), 0) As [date]
    From Table1
    Union All
    Select DateAdd(d, 1, [date])
    From Calendar
    Where [date] <= (
                    Select Max( DateAdd(d, DateDiff(d, 0, dateCol) + 1, 0) )
                    From Table1
                    )
    )
Select C.date, Count(Table1.PK) As Total
From Calendar As C
        Left Join Table1
            On Table1.dateCol >= C.date
                And Table1.dateCol < DateAdd(d, 1, C.date )
                And Table1.column2 = 'A'
Group By C.date
Option (Maxrecursion 0);

与其尝试在 SQL 中强制显示格式,不如在报表或图表生成器中执行此操作。但是,您可以在 SQL 中执行的操作是从日期时间值中删除时间部分,就像我在解决方案中所做的那样。

【讨论】:

我可能错了,但您似乎是根据 Day 进行分组的,而他在上面提供的日期表达式按月工作。虽然很酷的解决方案 @LorenVS - 当他将他的值转换为yyyy-MM-dd 的形式时,他怎么能按月分组? 非常酷。上面我的问题中的示例代码有点'黑客在一起,但在各种情况下,我按天、月或年分组,所以你的回答会很有帮助。 @Dan - 显然,一旦您可以在给定范围内获得所有天的结果,那么按月或年将它们汇总起来就相当容易了。 @Thomas - 当我读到他的表达时,它似乎只是生成 yyyy-MM,但我可能错了

以上是关于如何优化执行嵌套在 group-by 子句中的计数的 SQL 查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 Transact-sql 中的 group-by 子句中的项目设置类别?

具有子句mysql的嵌套计数?

嵌套连接

嵌套选择中的 WHERE 子句

子查询(嵌套子查询)

如何优化SQL语句