在Redshift / Postgres中,如何计算符合条件的行?

时间:2014-01-22 16:26:58

标签: postgresql amazon-redshift

我正在尝试编写一个只计算符合条件的行的查询。

例如,在MySQL中我会这样写:

SELECT
    COUNT(IF(grade < 70), 1, NULL)
FROM
    grades
ORDER BY
    id DESC;

但是,当我尝试在Redshift上执行此操作时,它会返回以下错误:

错误:函数if(布尔,整数,“未知”)不存在

提示:没有函数匹配给定的名称和参数类型。您可能需要添加显式类型转换。

我检查了条件语句的文档,我找到了

NULLIF(value1, value2)

但它只比较value1和value2,如果这些值相等,则返回null。

我找不到一个简单的IF语句,乍一看我找不到办法去做我想做的事。

我尝试使用CASE表达式,但是我没有得到我想要的结果:

SELECT 
    CASE
        WHEN grade < 70 THEN COUNT(rank)
        ELSE COUNT(rank)
    END
FROM
   grades

这是我想要计算的方式:

  • 失败(等级<70)

  • 平均值(70 <=等级<80)

  • 好(80 <=等级<90)

  • 优秀(90 <=等级<= 100)

这就是我期望看到结果的方式:

+========+=========+======+===========+
| failed | average | good | excellent |
+========+=========+======+===========+
|   4    |    2    |  1   |     4     |
+========+=========+======+===========+

但我得到了这个:

+========+=========+======+===========+
| failed | average | good | excellent |
+========+=========+======+===========+
|  11    |   11    |  11  |    11     |
+========+=========+======+===========+

我希望有人能指出我正确的方向!

如果这有助于这里的一些示例信息

CREATE TABLE grades(
  grade integer DEFAULT 0,
);

INSERT INTO grades(grade) VALUES(69, 50, 55, 60, 75, 70, 87, 100, 100, 98, 94);

4 个答案:

答案 0 :(得分:122)

首先,你在这里遇到的问题是你说的是“如果等级小于70,这个案例表达式的值是count(rank)。否则,这个表达式的值是count (秩)。”所以,在任何一种情况下,你总是得到相同的价值。

SELECT 
    CASE
        WHEN grade < 70 THEN COUNT(rank)
        ELSE COUNT(rank)
    END
FROM
   grades

count()只计算非空值,所以通常你会看到完成你正在尝试的模式是:

SELECT 
    count(CASE WHEN grade < 70 THEN 1 END) as grade_less_than_70,
    count(CASE WHEN grade >= 70 and grade < 80 THEN 1 END) as grade_between_70_and_80
FROM
   grades

这样,case表达式仅在测试表达式为true时计算为1,否则为null。然后count()将只计算非null实例,即当测试表达式为真时,它应该为您提供所需的。

编辑:作为旁注,请注意这与您最初使用count(if(test, true-value, false-value))编写此内容的方式完全相同,仅重写为count(case when test then true-value end)(并且null是错误的 - 由于未向案例提供else,因此值。

编辑:postgres 9.4在原始交换后几个月发布。该版本引入了聚合过滤器,可以使这样的场景看起来更好更清晰。这个答案仍然偶尔会有一些赞成,所以如果你偶然发现并且正在使用更新的postgres(即9.4+),你可能会想要考虑这个等效的版本:

SELECT
    count(*) filter (where grade < 70) as grade_less_than_70,
    count(*) filter (where grade >= 70 and grade < 80) as grade_between_70_and_80
FROM
   grades

答案 1 :(得分:11)

另一种方法:

SELECT 
    sum(CASE WHEN grade < 70 THEN 1 else 0 END) as grade_less_than_70,
    sum(CASE WHEN grade >= 70 and grade < 80 THEN 1 else 0 END) as grade_between_70_and_80
FROM
   grades

如果您想按分类列对计数进行分组,则工作正常。

答案 2 :(得分:2)

@yieldsfalsehood提供的解决方案可以完美地工作:

SELECT
    count(*) filter (where grade < 70) as grade_less_than_70,
    count(*) filter (where grade >= 70 and grade < 80) as grade_between_70_and_80
FROM
    grades

但是自从您谈论NULLIF(value1, value2)以来,nullif有一种方法可以提供相同的结果:

select count(nullif(grade < 70 ,true)) as failed from grades;

答案 3 :(得分:0)

仅Redshift

对于懒惰的打字员,这是一个构建在@ user1509107答案之上的“ COUNTIF”和整数转换版本:

SELECT 
    SUM((grade < 70)::INT) AS grade_less_than_70,
    SUM((grade >= 70 AND grade < 80)::INT) AS grade_between_70_and_80
FROM
   grades