【问题标题】:In Redshift/Postgres, how to count rows that meet a condition?在 Redshift/Postgres 中,如何计算满足条件的行?
【发布时间】:2014-02-12 20:05:44
【问题描述】:

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

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

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

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

错误:函数 if(boolean, integer, "unknown") 不存在

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

我检查了条件语句的文档,我发现了

NULLIF(value1, value2)

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

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

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

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

这就是我想计算事物的方式:

  • 失败(等级

  • 平均(70

  • 好(80

  • 优秀(90

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

+========+=========+======+===========+
| 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);

【问题讨论】:

标签: postgresql amazon-web-services amazon-redshift


【解决方案1】:

首先,您在这里遇到的问题是,您所说的是“如果成绩小于 70,则此 case 表达式的值为 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 表达式只会在测试表达式为真时计算为 1,否则为 null。然后 count() 只会计算非空实例,即当测试表达式为真时,这应该会给你你需要的。

编辑:作为旁注,请注意这与您最初使用 count(if(test, true-value, false-value)) 编写的方式完全相同,只是重写为 count(case when test then true-value end)(并且 null 是自 @ 以来的 false-value 的立场987654327@ 未提供给案例)。

编辑: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

【讨论】:

  • 太棒了!这正是我所需要的。虽然我觉得 postgres 没有提供简单的 IF 指令有点奇怪。我猜 CASE..WHEN..ELSE 做同样的工作,但仍然如此。 IF 几乎是行业标准。
  • 大小写是官方标准;) if()函数是MySQL,在SQL Server中就是iif()。
  • filter 显然也比其他选项更快,请参阅dba.stackexchange.com/questions/27558/…
【解决方案2】:

另一种方法:

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

如果您想按分类列对计数进行分组,效果很好。

【讨论】:

    【解决方案3】:

    @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 &lt; 70 ,true)) as failed from grades;

    【讨论】:

      【解决方案4】:

      仅限红移

      对于懒惰的打字员,这里有一个基于@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
      

      【讨论】:

      • 有趣的答案,绝对更难阅读和理解,并且依赖于布尔 true 转换为 1 和 false 转换为 0 的特定行为。我怀疑类型转换不如字面值那么高效数学案例语句表达式,但有兴趣查看一些基准。添加到答案列表中仍然有用
      • 这里有一些基准,postgres 中最好的选择是相对较新的filter 表达式dba.stackexchange.com/questions/27558/…
      猜你喜欢
      • 2015-03-21
      • 2020-08-29
      • 1970-01-01
      • 1970-01-01
      • 2021-02-05
      • 2013-10-14
      • 2023-02-14
      • 2016-05-09
      • 1970-01-01
      相关资源
      最近更新 更多