Postgresql LEFT JOIN json_agg() 忽略/删除 NULL

Posted

技术标签:

【中文标题】Postgresql LEFT JOIN json_agg() 忽略/删除 NULL【英文标题】:Postgresql LEFT JOIN json_agg() ignore/remove NULL 【发布时间】:2014-08-01 01:17:57 【问题描述】:
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)值替换右表列。结果,我得到了 [null] 作为 JSON 聚合之一。

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

干杯!

【问题讨论】:

【参考方案1】:

这种方法可行,但一定有更好的方法:(

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

【讨论】:

【参考方案2】:

可能是这样的吗?

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 fiddle demo

你也可以在加入前分组(我更喜欢这个版本,它更清晰一点):

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 fiddle demo

【讨论】:

感谢 Roman,我实际上想知道条件是否是最好的主意。这比使用COALESCE 或类似的东西快吗?只要将 emails 表转换为 JSON 作为 emails 字段,查询就必须成为 LEFT JOIN 想不出更快的方法来做到这一点,您可以尝试使用内部联接,然后与电子邮件中不存在contact.id 的联系人联合,但我怀疑这会更快..跨度> 【参考方案3】:

性能可能不如 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

【讨论】:

【参考方案4】:

我自己做了一个过滤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;
$$;

编辑:您可以在我的 gist 中看到一些实用程序(其中一些不是我的,但它们是从其他 *** 解决方案中获取的): https://gist.github.com/le-doude/8b0e89d71a32efd21283

【讨论】:

【参考方案5】:

如果这实际上是一个 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。您的来电。

【讨论】:

我在 12 岁,左连接仍然为空,你确定这是一个错误吗? 我确实说过“如果这是一个错误”。 5年后,这绝对不是错误。只是一个烦人的行为:( 这是左连接的产物,空值是实际值,而不是“无”【参考方案6】:

我使用了this answer(抱歉,我似乎无法链接到您的用户名)但我相信我改进了一点。

对于数组版本我们可以

    摆脱多余的双重选择 使用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,对于对象版本,我们可以:

    去掉不用的WITH子句 摆脱多余的双重选择

然后得到这个:

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 <> '""';
$$;

【讨论】:

【参考方案7】:

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

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://www.postgresql.org/docs/9.4/static/sql-expressions.html#SYNTAX-AGGREGATES

【讨论】:

这太棒了!它也适用于json_object_aggCOALESCE(json_object_agg(prop.key, prop.value) FILTER (WHERE prop.key IS NOT NULL), '')::json【参考方案8】:

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

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

...
array_remove(
    array_agg(jsonb_build_object('att1', column1, 'att2', column2)), 
    to_jsonb('"att1":null, "att2":null'::json)
)
...

【讨论】:

这正是我想要的。不明白为什么这不被接受,答案很简单,有效并且几乎成功了。还是谢谢

以上是关于Postgresql LEFT JOIN json_agg() 忽略/删除 NULL的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql left join左连接后数据变多去重记录(当左表1对多右表时)

MySQL LEFT JOIN json 字段与表中的另一个 id

LEFT OUTER JOIN with 'field IS NULL' in WHERE 用作 INNER JOIN

pg中join,left join的使用,将条件放到on和where后面的区别问题

带有 ON 子句或替代方法的休眠 LEFT JOIN FETCH

left join 需要distinct吗