sql-Postgresql左联接json_agg()忽略/删除NULL

SELECT C.id, C.name, json_agg(E) AS emails FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id;

例如Postgres 9.3创建输出

  id  |  name  |  emails
-----------------------------------------------------------
   1  |  Ryan  |  [{"id":3,"user_id":1,"email":"hello@world.com"},{"id":4,"user_id":1,"email":"again@awesome.com"}]
   2  |  Nick  |  [null]

由于我使用的是LEFT JOIN,因此有些情况下没有右表匹配项,因此用空(空)值代替右表列。 结果,我得到[null]作为JSON聚合之一。

当右表列为空时,如何忽略/删除null,以便有一个空的JSON数组[]

干杯!

user3081211 asked 2020-08-11T16:01:53Z
8个解决方案
63 votes

在9.4中,可以使用合并和聚合过滤器表达式。

SELECT C.id, C.name, 
  COALESCE(json_agg(E) FILTER (WHERE E.user_id IS NOT NULL), '[]') AS emails 
FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id, C.name
ORDER BY C.id;

筛选器表达式可防止聚合处理由于不满足左连接条件而导致处理空的行,因此最终将获得数据库空而不是json [null]。 一旦数据库为空,就可以照常使用合并。

[HTTP://呜呜呜.PostgreSQL.org/docs/9.4/static/SQL-expressions.HTML#syntax-aggregates]

Mike Stankavich answered 2020-08-11T16:02:08Z
13 votes

这样的事情可能是吗?

select
    c.id, c.name,
    case when count(e) = 0 then '[]' else json_agg(e) end as emails
from contacts as c
    left outer join emails as e on c.id = e.user_id
group by c.id

sql小提琴演示

您还可以在加入之前进行分组(我希望使用此版本,它会更清楚):

select
    c.id, c.name,
    coalesce(e.emails, '[]') as emails
from contacts as c
    left outer join (
        select e.user_id, json_agg(e) as emails from emails as e group by e.user_id
    ) as e on e.user_id = c.id

sql小提琴演示

Roman Pekar answered 2020-08-11T16:02:41Z
3 votes

如果这确实是PostgreSQL错误,我希望它已在9.4中修复。 很烦人。

SELECT C.id, C.name, 
  COALESCE(NULLIF(json_agg(E)::TEXT, '[null]'), '[]')::JSON AS emails 
FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id;

我个人不执行COALESCE位,只是返回NULL。 你的来电。

Jeff answered 2020-08-11T16:03:06Z
3 votes

我使用了这个答案(对不起,我似乎无法链接到您的用户名),但我相信我对此有所改进。

对于数组版本,我们可以

  1. 摆脱多余的双重选择
  2. 使用json_agg而不是array_to_json(array_agg())调用

并得到这个:

CREATE OR REPLACE FUNCTION public.json_clean_array(p_data JSON)
  RETURNS JSON
LANGUAGE SQL IMMUTABLE
AS $$
-- removes elements that are json null (not sql-null) or empty
SELECT json_agg(value)
  FROM json_array_elements(p_data)
 WHERE value::text <> 'null' AND value::text <> '""';
$$;

对于9.3,对于对象版本,我们可以:

  1. 摆脱未使用的WITH子句
  2. 摆脱多余的双重选择

并得到这个:

CREATE OR REPLACE FUNCTION public.json_clean(p_data JSON)
  RETURNS JSON
  LANGUAGE SQL IMMUTABLE
AS $$
-- removes elements that are json null (not sql-null) or empty
  SELECT ('{' || string_agg(to_json(key) || ':' || value, ',') || '}') :: JSON
    FROM json_each(p_data)
   WHERE value::TEXT <> 'null' AND value::TEXT <> '""';
$$;

对于9.4,我们不必使用字符串程序集构建对象,因为我们可以使用新添加的json_object_agg

CREATE OR REPLACE FUNCTION public.json_clean(p_data JSON)
  RETURNS JSON
  LANGUAGE SQL IMMUTABLE
AS $$
-- removes elements that are json null (not sql-null) or empty
  SELECT json_object_agg(key, value)
    FROM json_each(p_data)
   WHERE value::TEXT <> 'null' AND value::TEXT <> '""';
$$;
Developer.ca answered 2020-08-11T16:04:06Z
1 votes

性能可能不及Roman Pekar的解决方案,但更简洁:

select
c.id, c.name,
array_to_json(array(select email from emails e where e.user_id=c.id))
from contacts c
maniek answered 2020-08-11T16:04:27Z
1 votes

我做了自己的函数来过滤json数组:

CREATE OR REPLACE FUNCTION public.json_clean_array(data JSON)
  RETURNS JSON
LANGUAGE SQL
AS $$
SELECT
  array_to_json(array_agg(value)) :: JSON
FROM (
       SELECT
         value
       FROM json_array_elements(data)
       WHERE cast(value AS TEXT) != 'null' AND cast(value AS TEXT) != ''
     ) t;
$$;

我用它

select 
    friend_id as friend, 
    json_clean_array(array_to_json(array_agg(comment))) as comments 
from some_entity_that_might_have_comments 
group by friend_id;

当然仅在PostgreSQL 9.3中有效。 对于对象字段,我也有一个类似的例子:

CREATE OR REPLACE FUNCTION public.json_clean(data JSON)
  RETURNS JSON
LANGUAGE SQL
AS $$
SELECT
  ('{' || string_agg(to_json(key) || ':' || value, ',') || '}') :: JSON
FROM (
       WITH to_clean AS (
           SELECT
             *
           FROM json_each(data)
       )
       SELECT
         *
       FROM json_each(data)
       WHERE cast(value AS TEXT) != 'null' AND cast(value AS TEXT) != ''
     ) t;
$$;

编辑:在我的主旨中,您可以在此处看到一些实用程序(最初不是我的一些,但它们是从其他stackoverflow解决方案中提取的):[https://gist.github.com/le-doude/8b0e89d71a32efd21283]

ɭɘ ɖɵʊɒɼɖ 江戸 answered 2020-08-11T16:05:00Z
0 votes

这种方法有效,但是有更好的方法:(

SELECT C.id, C.name, 
  case when exists (select true from emails where user_id=C.id) then json_agg(E) else '[]' end
FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id, C.name;

演示:[http://sqlfiddle.com/#!15/ddefb/16]

Fabricator answered 2020-08-11T16:05:25Z
0 votes

有点不同,但可能对其他人有帮助:

如果数组中的所有对象都具有相同的结构(例如,因为您使用jsonb_build_object创建它们),则可以定义“具有相同结构的NULL对象”以用于array_remove

...
array_remove(
    array_agg(jsonb_build_object('att1', column1, 'att2', column2)), 
    to_jsonb('{"att1":null, "att2":null}'::json)
)
...
tom answered 2020-08-11T16:05:50Z
translate from https://stackoverflow.com:/questions/24155190/postgresql-left-join-json-agg-ignore-remove-null