如何使用postgres在array_agg中排除空值,例如在string_agg中?


96

如果使用array_agg收集名称,我的名称将以逗号分隔,但是如果有null值,则该null也将被用作集合中的名称。例如 :

SELECT g.id,
       array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
       array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
FROM groups g
GROUP BY g.id;

它返回,Larry,Phil而不是仅仅返回Larry,Phil(在我的9.1.2中显示NULL,Larry,Phil)。就像这个小提琴

相反,如果我使用string_agg(),它显示我只有名字(不包括空逗号或空)喜欢这里

问题是我已经Postgres 8.4安装在服务器上,并且string_agg()无法在该服务器上工作。有什么方法可以使array_agg类似于string_agg()吗?


有关这个主题的更多信息,请参见PostgreSQL邮件列表线程:postgresql.1045698.n5.nabble.com/…–
Craig Ringer

我很抱歉,我不认为这是在该线程的解决方案..
达乌德

该线程有两种解决方案。一种是创建函数,另一种(只是建议未显示)是我回答的一种。
Clodoaldo Neto 2012年

@Clodoaldo-所有行将在('y','n')中具有规范...因此where子句似乎是多余的。问题是,一个分组里面,如果规范场的值是“Y”,我们正在收集“N的,然后一空收集太..
达乌德

好。现在我懂了。检查更新答案。
Clodoaldo Neto 2012年

Answers:


28

SQL小提琴

select
    id,
    (select array_agg(a) from unnest(canonical_users) a where a is not null) canonical_users,
    (select array_agg(a) from unnest(non_canonical_users) a where a is not null) non_canonical_users
from (
    SELECT g.id,
           array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
           array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
    FROM groups g
    GROUP BY g.id
) s

或者,使用更简单且可能更便宜的方法array_to_string来消除null:

SELECT
    g.id,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END)
        , ','
    ) canonical_users,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END)
        , ','
    ) non_canonical_users
FROM groups g
GROUP BY g.id

SQL小提琴


谢谢。但是,如果主查询返回1000行,则2个子查询(使用unnest)将为每行运行一次。与执行2000个额外的select查询相比,容忍NULL会更好吗?
达德(Daud)2012年

@Daud可能更便宜的新版本。确定两者的说明输出。
克洛多尔多·内图

3
@Clodoaldo如果您使用的array_to_string(array_agg(...))话,不妨使用string_agg
克雷格·林格2012年

1
@Craig问题中的问题是8.4
Clodoaldo Neto

@Clodoaldo Gah,旧版本。谢谢。
Craig Ringer 2012年

245

使用postgresql-9.3可以做到这一点。

SELECT g.id,
   array_remove(array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END), NULL) canonical_users,
   array_remove(array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END), NULL) non_canonical_users
FROM groups g 
GROUP BY g.id;

更新:使用postgresql-9.4;

SELECT g.id,
   array_agg(g.users) FILTER (WHERE g.canonical = 'Y') canonical_users,
   array_agg(g.users) FILTER (WHERE g.canonical = 'N') non_canonical_users
FROM groups g 
GROUP BY g.id;

5
它的工作原理是快速而优雅的,它解决了我与OP相似的问题。尚未升级到9.3的原因。+1
Pavel V.

12
9.4更加优雅。像魅力一样工作
jmgarnier 2015年

2
9.4变体甚至更好,因为我需要过滤掉的是空值。
coladict

我首先使用了更新的版本,但随后意识到我需要删除Null和重复项,因此回到了第一个建议。这是一个很大的查询,但是它是要创建一个物化视图,所以不是一个大问题。
Rerequestual's

12

在解决从数组集合中删除空值的一般问题时,有两种主要的方法可以解决该问题:执行array_agg(unnest(array_agg(x))或创建自定义集合。

第一种是上面显示的形式:

SELECT 
    array_agg(u) 
FROM (
    SELECT 
        unnest(
            array_agg(v)
        ) as u 
    FROM 
        x
    ) un
WHERE 
    u IS NOT NULL;

第二:

/*
With reference to
http://ejrh.wordpress.com/2011/09/27/denormalisation-aggregate-function-for-postgresql/
*/
CREATE OR REPLACE FUNCTION fn_array_agg_notnull (
    a anyarray
    , b anyelement
) RETURNS ANYARRAY
AS $$
BEGIN

    IF b IS NOT NULL THEN
        a := array_append(a, b);
    END IF;

    RETURN a;

END;
$$ IMMUTABLE LANGUAGE 'plpgsql';

CREATE AGGREGATE array_agg_notnull(ANYELEMENT) (
    SFUNC = fn_array_agg_notnull,
    STYPE = ANYARRAY,
    INITCOND = '{}'
);

调用第二个(自然地)比第一个好看:

从x中选择array_agg_notnull(v);


9

即使此线程很旧,我也要添加此代码,但是我遇到了这个巧妙的技巧,该技巧在小型阵列上效果很好。它可以在Postgres 8.4+上运行,而无需其他库或函数。

string_to_array(array_to_string(array_agg(my_column)))::int[]

array_to_string()方法实际上摆脱了空值。


8

如果您正在寻找有关如何从数组中删除NULL的一般问题的现代答案,则为:

array_remove(your_array, NULL)

我对性能特别好奇,并想将其与最佳替代方案进行比较:

CREATE OR REPLACE FUNCTION strip_nulls(
    IN array_in ANYARRAY
)
RETURNS anyarray AS
'
SELECT
    array_agg(a)
FROM unnest(array_in) a
WHERE
    a IS NOT NULL
;
'
LANGUAGE sql
;

进行pgbench测试证明(具有高置信度)array_remove()快两倍多。我对具有各种数组大小(10、100和1000个元素)以及介于两者之间的随机NULL的双精度数字进行了测试。


@VivekSinha您使用的是哪个版本的postgres?我刚刚测试了您的查询,结果为我生成了“ {1,2,3}”。我正在使用12.1。
Alexi Theodore

啊,我看到@ alexi-theodore快要结束了。我正在使用自定义+修改过的postgres驱动程序。当我直接在控制台中查询时,我可以看到正确的输出!对不起,我很困惑。删除先前的评论并选择答案!
Vivek Sinha

3

正如注释中所建议的那样,您可以编写一个函数来替换数组中的空值,但是,正如注释中链接的线程所指出的那样,如果必须创建一个聚合,则这种方法会破坏聚合函数的效率。 ,将其拆分,然后再次汇总。

我认为在数组中保留空值只是Array_Agg的(也许是不需要的)功能。您可以使用子查询来避免这种情况:

SELECT  COALESCE(y.ID, n.ID) ID,
        y.Users,
        n.Users
FROM    (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'Y'
            GROUP BY g.ID
        ) y
        FULL JOIN 
        (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'N'
            GROUP BY g.ID
        ) n
            ON n.ID = y.ID

SQL字段


谢谢。但是我需要用“ case”来处理给定组中的行,那里的子查询效率很低
Daud 2012年

0

这非常简单,首先要为text []创建一个新的-(减号)运算符:

CREATE OR REPLACE FUNCTION diff_elements_text
    (
        text[], text[] 
    )
RETURNS text[] as 
$$
    SELECT array_agg(DISTINCT new_arr.elem)
    FROM
        unnest($1) as new_arr(elem)
        LEFT OUTER JOIN
        unnest($2) as old_arr(elem)
        ON new_arr.elem = old_arr.elem
    WHERE old_arr.elem IS NULL
$$ LANGUAGE SQL IMMUTABLE;

CREATE OPERATOR - (
    PROCEDURE = diff_elements_text,
    leftarg = text[],
    rightarg = text[]
);

并简单地减去array [null]:

select 
    array_agg(x)-array['']
from
    (   select 'Y' x union all
        select null union all
        select 'N' union all
        select '' 
    ) x;

就这样:

{Y,N}


array_agg(x) FILTER (WHERE x is not null)似乎容易得多dbfiddle.uk/…,而您实际上并不需要自己的功能,只需使用array_remove() dbfiddle.uk/…–
a_horse_with_no_name

-6

更大的问题是为什么要一次拉所有用户/组组合。保证您的UI无法处理所有这些数据。向超大数据添加分页也是一个坏主意。让您的用户在看到数据之前对其进行过滤。确保您的JOIN选项集在列表中,以便他们可以根据需要过滤性能。有时,如果两个查询都很快,就会使用户感到更快乐。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.