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_agg
:COALESCE(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后面的区别问题