GROUP BY + CASE 语句

Posted

技术标签:

【中文标题】GROUP BY + CASE 语句【英文标题】:GROUP BY + CASE statement 【发布时间】:2013-11-19 20:45:51 【问题描述】:

我有一个工作查询,它​​按硬件型号和结果对数据进行分组,但问题是有很多“结果”。我试图将其减少到 “如果结果 = 0 然后保持为 0,否则将其设置为 1”。这通常有效,但我最终有:

    day     |      name      | type | case | count
------------+----------------+------+------+-------
 2013-11-06 | modelA         |    1 |    0 |   972
 2013-11-06 | modelA         |    1 |    1 |    42
 2013-11-06 | modelA         |    1 |    1 |     2
 2013-11-06 | modelA         |    1 |    1 |    11
 2013-11-06 | modelB         |    1 |    0 |   456
 2013-11-06 | modelB         |    1 |    1 |    16
 2013-11-06 | modelB         |    1 |    1 |     8
 2013-11-06 | modelB         |    3 |    0 | 21518
 2013-11-06 | modelB         |    3 |    1 |     5
 2013-11-06 | modelB         |    3 |    1 |     7
 2013-11-06 | modelB         |    3 |    1 |   563

而不是我试图实现的聚合,每个类型/案例组合只有 1 行。

    day     |      name      | type | case | count
------------+----------------+------+------+-------
 2013-11-06 | modelA         |    1 |    0 |   972
 2013-11-06 | modelA         |    1 |    1 |    55
 2013-11-06 | modelB         |    1 |    0 |   456
 2013-11-06 | modelB         |    1 |    1 |    24
 2013-11-06 | modelB         |    3 |    0 | 21518
 2013-11-06 | modelB         |    3 |    1 |   575

这是我的查询:

select CURRENT_DATE-1 AS day, model.name, attempt.type, 
       CASE WHEN attempt.result = 0 THEN 0 ELSE 1 END, 
       count(*) 
from attempt attempt, prod_hw_id prod_hw_id, model model
where time >= '2013-11-06 00:00:00'  
AND time < '2013-11-07 00:00:00'
AND attempt.hard_id = prod_hw_id.hard_id
AND prod_hw_id.model_id = model.model_id
group by model.name, attempt.type, attempt.result
order by model.name, attempt.type, attempt.result;

任何关于我如何实现这一点的提示都很棒。

Day 将始终在WHERE 子句中定义,因此不会发生变化。 name, type, result(case)count 会有所不同。简而言之,对于任何给定的模型,每个 "type + case" 组合我只需要 1 行。正如您在第一个结果集中看到的那样,我有 3 行 modelAtype=1case=1(因为有很多 "result" 值我已经变成了 0=0 和其他任何值=1)。我希望将其表示为 1 行,并在示例数据集 2 中聚合计数。

【问题讨论】:

哪个行每种类型/案例。定义你的选择。日期和名称可以不同吗? @ErwinBrandstetter,对于 1 个模型(名称),每个类型/结果(案例)组合只有 1 行。有很多型号。 每个模型总是至少有 1 行,但由于 succ/fail 和不同的类型,可能会更多。我将在数据集示例中添加更多内容。也许这会更有帮助。我正在尝试按型号、类型、案例进行分组;是的。通常它可以工作,但我只是想在只有计数不同的(否则)唯一行上聚合计数。 【参考方案1】:

只有在前面的步骤中引入别名才能使用。所以SELECT 子句中的别名可以在ORDER BY 中使用,但不能在GROUP BY 子句中使用。

参考:Microsoft T-SQL 文档供进一步阅读。

FROM
ON
JOIN
WHERE
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP

希望这会有所帮助。

【讨论】:

【参考方案2】:

对于 TSQL,我喜欢将 case 语句封装在外部应用中。这使我不必将 case 语句编写两次,允许在以后的连接中通过别名引用 case 语句,并避免了对位置引用的需要。

select oa.day, 
model.name, 
attempt.type, 
oa.result
COUNT(*) MyCount 
FROM attempt attempt, prod_hw_id prod_hw_id, model model
WHERE time >= '2013-11-06 00:00:00'  
AND time < '2013-11-07 00:00:00'
AND attempt.hard_id = prod_hw_id.hard_id
AND prod_hw_id.model_id = model.model_id
OUTER APPLY (
    SELECT CURRENT_DATE-1 AS day,
     CASE WHEN attempt.result = 0 THEN 0 ELSE 1 END result
    ) oa    
group by oa.day, 
model.name, 
attempt.type, 
oa.result
order by model.name, attempt.type, oa.result;

【讨论】:

TSQL 如何适用于 [postgresql] 问题? (不是。) 奇怪的是 ^^ 当我用谷歌搜索如何通过这个答案避免组中重复的 case 语句时帮助了我(对于 tSQL 是的:D)【参考方案3】:

你能试试这个吗: 用下面的替换case语句

Sum(CASE WHEN attempt.result = 0 THEN 0 ELSE 1 END) as Count,

【讨论】:

我不知道你可以用 sum 包装一个案例,并通过遵循复杂案例陈述的噩梦来避免分组。可能性...【参考方案4】:

您的查询已经可以工作了 - 除非您遇到命名冲突或只是将 输出列CASE 表达式)与 源列 result 混淆, 内容不同。

...
GROUP BY model.name, attempt.type, attempt.result
...

您需要GROUP BY 您的CASE 表达式而不是您的源列:

...
GROUP BY model.name, attempt.type
       , CASE WHEN attempt.result = 0 THEN 0 ELSE 1 END
...

或者提供一个不同于FROM 列表中任何列名的列别名 - 否则该列优先:

SELECT ...
     , CASE WHEN attempt.result = 0 THEN 0 ELSE 1 END AS result1
...
GROUP BY model.name, attempt.type, result1
...

SQL 标准在这方面比较特殊。 Quoting the manual here:

输出列的名称可用于引用该列的值 ORDER BYGROUP BY 子句,但不在 WHEREHAVING 子句中; 在那里你必须写出表达式。

还有:

如果 ORDER BY 表达式是一个简单的名称,它同时匹配输出 列名和输入列名,ORDER BY 将其解释为 输出列名。 这与GROUP BY的选择相反 会在同样的情况下。这种不一致是 与 SQL 标准兼容。

粗体强调我的。

可以通过在GROUP BYORDER BY 中使用位置引用(序数)来避免这些冲突,从左到右引用SELECT 列表中的项目。请参阅下面的解决方案。 缺点是,这可能更难阅读并且容易受到SELECT 列表中的编辑的影响(人们可能会忘记相应地调整位置引用)。

但是您不必必须将列day 添加到GROUP BY 子句中,只要它具有恒定值(CURRENT_DATE-1)。

用正确的 JOIN 语法和位置引用重写和简化它可能看起来像这样:

SELECT m.name
     , a.type
     , CASE WHEN a.result = 0 THEN 0 ELSE 1 END AS result
     , CURRENT_DATE - 1 AS day
     , count(*) AS ct
FROM   attempt    a
JOIN   prod_hw_id p USING (hard_id)
JOIN   model      m USING (model_id)
WHERE  ts >= '2013-11-06 00:00:00'  
AND    ts <  '2013-11-07 00:00:00'
GROUP  BY 1,2,3
ORDER  BY 1,2,3;

另外请注意,我避免使用列名time。那是reserved word,永远不应该用作标识符。此外,您的“时间”显然是timestamp or date,所以这是相当误导。

【讨论】:

在谈论命名冲突时,您可能应该提到使用位置(数字)列引用的选项,而不是仅仅使用与散文建议不匹配的示例。我自己仍然觉得这种语法的可读性要差得多,但我知道有些人更喜欢它。 另外解释得很好,顺便说一句;很好地引用了手册(因此间接引用了标准),了解何时以及如何使用输出列名。 @IMSoP:嗯..但我确实在最后提到了位置参数。我已经在这个答案中投入了很多,所以我保持简短。读者可以转向手册进行更多史诗般的阅读。链接在那里.. 长度足够。在谈论如何命名输出列以完全不实际使用输出列名称,而是完全不同的问题解决方案之后,感觉很奇怪。也许就在示例之前说“重写......使用另一种替代方法,即按位置引用输出列”或其他内容。 (另外,这些肯定不是位置 参数?只是位置 references...) @IMSoP:你说得对,这个词具有误导性。相应更新。【参考方案5】:

尝试将另外两个非 COUNT 列添加到 GROUP BY:

select CURRENT_DATE-1 AS day, 
model.name, 
attempt.type, 
CASE WHEN attempt.result = 0 THEN 0 ELSE 1 END, 
count(*) 
from attempt attempt, prod_hw_id prod_hw_id, model model
where time >= '2013-11-06 00:00:00'  
AND time < '2013-11-07 00:00:00'
AND attempt.hard_id = prod_hw_id.hard_id
AND prod_hw_id.model_id = model.model_id
group by 1,2,3,4
order by model.name, attempt.type, attempt.result;

【讨论】:

我假设您的意思是将“天”添加到组中?我试过了,结果是一样的。你的意思是别的吗?我在选择中有 5 个字段,其中 3 个已经存在,所以 day 是唯一的其他非计数字段。 是的。我把它们放在我的答案上:1,2,3,4。除 COUNT 之外的所有内容。

以上是关于GROUP BY + CASE 语句的主要内容,如果未能解决你的问题,请参考以下文章

Group by 语句不适用于 Select Case

如何使用 CASE 语句而不必将其放入 GROUP BY

带有表连接、case 语句、计数、group by 子句的 Linq 查询

Oracle SQL - 使用 CASE WHEN 语句时 GROUP BY 无效

得到 ORA-00979: 不是没有任何聚合函数的 GROUP BY 语句

sql group排序问题