如何在PostgreSQL sum()中检测NULL行

rob*_*obx 9 postgresql null sum aggregate-functions

我希望聚合列的总和,同时跟踪是否存在表示错误情况的NULL值.例如,取表号码:

# select * from numbers;
  n   |  l  
------+-----
    1 | foo
    2 | foo
 NULL | bar
    4 | bar
Run Code Online (Sandbox Code Playgroud)

给定一个标签l,我想计算n带有该标签的数字总和,前提是没有NULL值.理想情况下,对于没有任何行的一个标签,总和将是0,所以我在寻找一些查询q,从而 q('foo') = 3,q('baz') = 0q('bar')以某种方式发出错误信号,例如,通过返回NULL.

我从sum() 聚合函数开始,但是将NULL行转换为0.一个解决方案是返回,NULL只要有任何NULL值.

sum()

# select sum(n) from numbers where l = 'bar';
 sum 
-----
   4
Run Code Online (Sandbox Code Playgroud)

但我宁愿和你sumnull()在一起

# select sumnull(n) from numbers where l = 'bar';
 sumnull 
---------
  NULL
Run Code Online (Sandbox Code Playgroud)

到目前为止,我发现的最佳解决方案是计算非NULL行并与总计数进行比较:

# select sum(n), count(*), count(n) as notnull from numbers;
 sum | count | notnull 
-----+-------+---------
   7 |     4 |       3
Run Code Online (Sandbox Code Playgroud)

如果count不相等notnull,我知道结果无效.

Clo*_*eto 9

空集是否足够好?

create table numbers (n int);
insert into numbers values (1),(2),(null),(4);

select sum(n)
from numbers
having bool_and(n is not null);
 sum 
-----
(0 rows)
Run Code Online (Sandbox Code Playgroud)

如果你真的需要一个空值,它会有点复杂:

with sum_null as (
    select sum(n) as sum_n
    from numbers
    having bool_and(n is not null)
)
select case
    when not exists (select 1 from sum_null) then null
    else (select sum_n from sum_null) end
;
 sum_n 
-------

(1 row)
Run Code Online (Sandbox Code Playgroud)

替换having为:

having not bool_or(n is null)
Run Code Online (Sandbox Code Playgroud)

不太可读但可能更快,因为它可以在第一次null找到时停止搜索.

https://www.postgresql.org/docs/current/static/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE


kli*_*lin 7

您可以创建自定义聚合,例如:

create or replace function int_sum_null(int, int)
returns int language sql as $$
    select $1 + $2
$$;

create aggregate sumnull(integer) (
    sfunc = int_sum_null,
    stype = int
);

select sum(n), sumnull(n)
from numbers;

 sum | sumnull 
-----+---------
   7 | <null>      
(1 row) 
Run Code Online (Sandbox Code Playgroud)

更新#1

没有自定义聚合的解决方案

select case 
    when bool_or(n is null) then null 
    else sum(n) end
from numbers;

select coalesce((
    select sum(n)
    from numbers
    having not bool_or(n is null)), null);
Run Code Online (Sandbox Code Playgroud)

这些变体基于Clodoaldo Neto的想法.如果你喜欢他们,请同意他的回答.


更新#2

修改自定义聚合sumnull并添加初始条件:

drop aggregate sumnull(integer);
create aggregate sumnull(integer) (
    sfunc = int_sum_null,
    stype = int,
    initcond = 0
);
Run Code Online (Sandbox Code Playgroud)

获取您在更新的问题中描述的结果:

create table numbers (n int, l text);
insert into numbers values 
(1, 'foo'), (2, 'foo'), (null, 'bar'), (4, 'bar');

select 
    sumnull(n) filter (where l = 'foo') foo, 
    sumnull(n) filter (where l = 'bar') bar, 
    sumnull(n) filter (where l = 'baz') baz
from numbers;

 foo | bar | baz 
-----+-----+-----
   3 |     |   0
(1 row)
Run Code Online (Sandbox Code Playgroud)