web-dev-qa-db-fra.com

Postgresql LEFT JOIN json_agg () ignorer/supprimer 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 crée une sortie par exemple

  id  |  name  |  emails
-----------------------------------------------------------
   1  |  Ryan  |  [{"id":3,"user_id":1,"email":"[email protected]"},{"id":4,"user_id":1,"email":"[email protected]"}]
   2  |  Nick  |  [null]

Comme j'utilise un LEFT JOIN, il y aura des cas où il n'y a pas de correspondance dans le tableau de droite, c'est pourquoi des valeurs vides (null) sont substituées aux colonnes du tableau de droite. En conséquence, je reçois [null] en tant que l’un des agrégats JSON.

Comment puis-je ignorer/supprimer null afin d'avoir un tableau JSON vide [] lorsque la colonne de la table de droite est null?

À votre santé!

31
user3081211

En 9.4, vous pouvez utiliser coalesce et une expression de filtre agrégée.

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;

L'expression de filtre empêche l'assemblage de traiter les lignes nulles car la condition de jointure gauche n'est pas remplie. Vous obtenez donc une base de données null à la place de json [null]. Une fois que vous avez une base de données null, vous pouvez utiliser coalesce comme d’habitude.

http://www.postgresql.org/docs/9.4/static/sql-expressions.html#SYNTAX-AGGREGATES

41
Mike Stankavich

alors etwas kann sein?

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

vous êtes ici

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

12
Roman Pekar

S'il s'agit en fait d'un bogue PostgreSQL, j'espère qu'il a été corrigé dans la version 9.4. Très ennuyant.

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;

Personnellement, je ne fais pas le bit COALESCE, il suffit de renvoyer la valeur NULL. Ton appel.

3
Jeff

J'ai utilisé cette réponse (désolé, je n'arrive pas à créer un lien vers votre nom d'utilisateur), mais je pense l'avoir un peu améliorée.

Pour la version tableau nous pouvons

  1. se débarrasser de la double sélection redondante
  2. utilisez json_agg au lieu des appels array_to_json(array_agg())

et obtenez ceci:

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

Pour 9.3, pour la version objet, nous pouvons:

  1. se débarrasser de la clause WITH non utilisée
  2. se débarrasser de la double sélection redondante

et obtenez ceci:

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

Pour la version 9.4, il n'est pas nécessaire d'utiliser la chaîne Assembly pour construire l'objet, car nous pouvons utiliser le json_object_agg récemment ajouté.

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 <> '""';
$$;
3
Developer.ca

J'ai créé ma propre fonction pour filtrer les tableaux 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;
$$;

Je l'utilise comme

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;

bien sûr, ne fonctionne que dans postgresql 9.3. J'ai aussi un similaire pour les champs d'objet:

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: Vous pouvez voir quelques utils (quelques-uns ne sont pas les miens à l’origine mais ils ont été empruntés à d’autres solutions de stackoverflow) ici sur mon Gist: https://Gist.github.com/le-doude/8b0e89d71a32efd21283

Probablement moins performant que la solution de Roman Pekar, mais un peu plus net:

select
c.id, c.name,
array_to_json(array(select email from emails e where e.user_id=c.id))
from contacts c
1
maniek

Cela fonctionne, mais il doit y avoir un meilleur moyen :(

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;

démo: http://sqlfiddle.com/#!15/ddefb/16

0
Fabricator

Un peu différent mais pourrait être utile pour les autres:

Si tous les objets du tableau ont la même structure (par exemple parce que vous utilisez jsonb_build_object pour les créer), vous pouvez définir un "objet NULL avec la même structure" à utiliser dans array_remove:

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