Подтвердить что ты не робот

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?

Ура!

4b9b3361

Ответ 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