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

时间:2012-10-29 13:49:56

标签: sql postgresql postgresql-9.1 postgresql-8.4

如果我使用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)。如this小提琴

相反,如果我使用string_agg(),它只显示名称(没有空逗号或空值),例如here

问题是我在服务器上安装了Postgres 8.4,而string_agg()在那里不起作用。 有没有办法让array_agg与string_agg()类似?

9 个答案:

答案 0 :(得分:182)

使用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;

答案 1 :(得分:18)

SQL Fiddle

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

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 Fiddle

答案 2 :(得分:14)

如果您要寻找有关如何从数组中删除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的双精度数字进行了测试。


还值得注意的是,这可用于删除空格(“!= NULL”)。但是第二个参数接受anyelement,并且由于它们很可能是用字符串文字来表示空格,因此请确保将其强制转换为所需的形式,通常是非数组。

例如:

select array_remove(array['abc', ''], ''::text);

如果您尝试:

select array_remove(array['abc', ''], '');

它将假定''为TEXT [](数组),并将引发此错误:

错误:格式不正确的数组文字:“”

答案 3 :(得分:11)

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

第一种是above所示的形式:

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)

答案 4 :(得分:7)

我正在添加这个,即使这个线程很老,但我遇到了这个在小数组上运行良好的巧妙技巧。它在Postgres 8.4+上运行,没有额外的库或函数。

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

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

答案 5 :(得分: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 FIDDLE

答案 6 :(得分:3)

你应该用 array_remove 包裹你的 array_agg

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;

答案 7 :(得分: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[]
);

然后简单地减去数组[null]:

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

仅此而已

{Y,N}

答案 8 :(得分:-3)

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