使用SUM聚合的Postgres CASE条件评估不需要ELSE部分

时间:2014-10-10 10:13:16

标签: sql postgresql

根据Postgres documentation

  

CASE表达式不会评估任何非表达式的子表达式   需要确定结果。例如,这是一种可能的方式   避免零切失败:

     

SELECT ... WHERE CASE WHEN<> 0那么y / x> 1.5 ELSE false END;

为什么以下表达式返回ERROR: division by zero? - 显然正在评估else部分:

SELECT CASE WHEN SUM(0) = 0 THEN 42 ELSE 43 / 0 END

,而

SELECT CASE WHEN SUM(0) = 0 THEN 42 ELSE 43 END

返回42。

编辑:所以上面的例子失败了,因为Postgres已经在计划阶段计算了不可变值(43/0)。我们的实际查询看起来更像是这样:

case when sum( column1 ) = 0
            then 0
            else round( sum(   price 
                             * hours 
                             / column1 ), 2 )

虽然此查询看起来不可变(取决于实际值),但仍然存在除零错误。当然,sum(column1)实际上是0。

1 个答案:

答案 0 :(得分:3)

有趣的例子。这确实有一个很好的解释。假设你有这样的数据:

db=# table test;
 column1 | price | hours 
---------+-------+-------
       1 |     2 |     3
       3 |     2 |     1

PostgreSQL在两次传递中执行你的SELECT,首先它会计算出现的所有聚合函数(如sum()):

db=# select sum(column1) as sum1, sum(price * hours / column1) as sum2 from test;
 sum1 | sum2 
------+------
    4 |    6

然后它会将这些结果插入最终表达式并计算实际结果:

db=# with temp as (
db(#     select sum(column1) as sum1, sum(price * hours / column1) as sum2 from test
db(# ) select case when sum1 = 0 then 0 else round(sum2, 2) end from temp;
 round 
-------
  6.00

现在很清楚,如果第一次聚合传递中出现错误,它就永远不会到达CASE语句。

所以这在CASE语句的文档中并不是真正的问题 - 它适用于所有条件结构 - 但是关于在SELECT语句中处理聚合的方式。这种问题不会出现在任何其他上下文中,因为聚合只允许在SELECT中使用。

但是在这种情况下文档确实需要更新。此实例中的正确文档是“the general processing of SELECT”。步骤#4讨论了GROUP BY和HAVING子句,但它实际上也评估了此步骤中的任何聚合函数,无论GROUP BY / HAVING如何。并且您的CASE语句将在步骤#5中进行评估。

解决方案

常见的解决方案是,如果要忽略否则会导致除数为零的聚合输入,请使用nullif() construct将它们转换为NULL:

round( sum(   price 
            * hours 
            / nullif(column1, 0) ), 2 )

PostgreSQL 9.4将为聚合引入一个新的FILTER子句,它也可以用于此目的:

round( sum(   price 
            * hours 
            / column1
          ) filter (where column1!=0), 2 )