简体   繁体   English

带SUM聚合的Posgtres CASE条件评估不需要ELSE部分

[英]Posgtres CASE condition with SUM aggregation evaluates not needed ELSE part

According to Postgres documentation : 根据Postgres文件

A CASE expression does not evaluate any subexpressions that are not needed to determine the result. CASE表达式不会评估确定结果所不需要的任何子表达式。 For example, this is a possible way of avoiding a division-by-zero failure: 例如,这是避免零除失败的可能方法:

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

Why does the following expression return an ERROR: division by zero? 为什么以下表达式返回ERROR: division by zero? - apparently evaluating the else part: - 显然评估了其他部分:

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

while

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

returns 42. 返回42。

EDIT: So the example above fails because Postgres calculates immutable values (43/0) already in planning phase. 编辑:所以上面的例子失败了,因为Postgres已经计划阶段计算不可变值(43/0)。 Our actual query looks more like this: 我们的实际查询看起来更像是这样:

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

Although this query doesn't look immutable (depends on actual values), there is still a division by zero error. 虽然此查询看起来不可变(取决于实际值),但仍然存在除零错误。 Of course sum(column1) is actually 0 in our case. 当然sum(column1)在我们的例子中实际上是0。

Interesting example. 有趣的例子。 This does have a good explanation. 这确实有一个很好的解释。 Say you have data like this: 假设你有这样的数据:

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

PostgreSQL executes your SELECT in two passes, first it would calculate all the aggregate functions (like sum() ) present: PostgreSQL在两次传递中执行SELECT,首先它将计算所有聚合函数(如sum() ):

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

And then it would plug those results in your final expression and calculate the actual result: 然后它会将这些结果插入到最终表达式中并计算实际结果:

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

Now clearly if there's an error in the first aggregate pass, it never reaches the CASE statement. 现在很清楚,如果第一次聚合传递中出现错误,它就永远不会到达CASE语句。

So this isn't really a problem in documentation about the CASE statement -- it applies to all conditional constructs -- but about the way aggregates are processed in a SELECT statement. 所以这在关于CASE语句的文档中并不是真正的问题 - 它适用于所有条件结构 - 但是关于在SELECT语句中处理聚合的方式。 This kind of problem cannot occur in any other context because aggregates are only allowed in SELECT. 在任何其他上下文中都不会发生此类问题,因为聚合仅在SELECT中允许。

But the documentation does need updating in this case too. 但在这种情况下,文档确实需要更新。 The right documentation in this instance is " the general processing of SELECT ". 本例中的正确文档是“ SELECT的一般处理 ”。 Step #4 there talks about GROUP BY and HAVING clauses, but it actually evaluates any aggregate functions in this step as well, regardless of GROUP BY/HAVING. 步骤#4讨论了GROUP BY和HAVING子句,但它实际上也评估了此步骤中的任何聚合函数,无论GROUP BY / HAVING如何。 And your CASE statement is evaluated in step #5. 并且您的CASE语句将在步骤#5中进行评估。

Solution

The common solution, if you want to ignore aggregate inputs that would otherwise cause a division by zero, use the nullif() construct to turn them into NULLs: 常见的解决方案是,如果要忽略否则会导致除零的聚合输入,请使用nullif()构造将它们转换为NULL:

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

PostgreSQL 9.4 will introduce a new FILTER clause for aggregates, which can also be used for this purpose: PostgreSQL 9.4将为聚合引入一个新的FILTER子句,它也可用于此目的:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM