Postgresql LEFT JOIN 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":"[email protected]"},{"id":4,"user_id":1,"email":"[email protected]"}]
2 | Nick | [null]
Поскольку я использую LEFT JOIN, будут случаи, когда нет соответствия правого стола, поэтому для столбцов правой таблицы заменяются пустые (нулевые) значения. В результате я получаю [null]
как одну из агрегатов JSON.
Как я могу игнорировать/удалить null
, поэтому у меня есть пустой массив JSON []
, когда столбец правой таблицы равен null?
Ура!
Ответы
Ответ 1
В 9.4 вы можете использовать выражение coalesce и aggregate filter.
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]. Когда у вас есть нулевой байт, вы можете использовать coalesce, как обычно.
http://www.postgresql.org/docs/9.4/static/sql-expressions.html#SYNTAX-AGGREGATES
Ответ 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
вы также можете группировать перед присоединением (я бы предпочел эту версию, это немного более понятно):
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
Ответ 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;
$$;
EDIT: вы можете увидеть несколько utils (некоторые из них не изначально мои, но они были взяты из других решений stackoverflow) здесь, по моему принципу:
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. Ваш звонок.
Ответ 6
Я использовал этот ответ (извините, я не могу ссылаться на ваше имя пользователя), но я считаю, что немного улучшил его.
Для версии массива мы можем
- избавиться от дублированного двойного выбора
- используйте 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
Этот способ работает, но там должен быть лучший способ: (
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;
demo: http://sqlfiddle.com/#!15/ddefb/16