2014-06-11 14 views
25
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 crea uscita per esempioPostgreSQL LEFT JOIN json_agg() ignorano/rimuovere NULL

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

Come sto usando un LEFT JOIN non ci saranno casi in cui non v'è alcuna corrispondenza destro del tavolo quindi vuota (null) i valori sono sostituiti per le colonne della tabella di destra. Di conseguenza ottengo [null] come uno degli aggregati JSON.

Come posso ignorare/rimuovere null quindi ho un array JSON vuoto [] quando la colonna della tabella di destra è nullo?

Cheers!

risposta

0

In questo modo funziona, ma ci deve essere un modo migliore :(

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

11

qualcosa come questo, può essere

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

è anche possibile gruppo prima aderire (preferisco questa versione, è un po 'più chiaro):

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

+0

Grazie romana, mi è stato effettivamente chiedendo se un condizionale sarebbe l'idea migliore. È più veloce dell'uso di un 'COALESCE' o qualcosa di simile? La query non deve essere un "SINISTRA UNISCI" purché la tabella dei messaggi di posta elettronica sia trasformata in JSON come campo "email". – user3081211

+0

non riesco a pensare a un modo più veloce per farlo, puoi provare ad usare inner join e quindi union con i contatti dove contact.id non esiste nelle email, ma dubito che questo sarà più veloce .. –

1

Probabilmente meno performante rispetto alla soluzione di Roman Pekar, ma un po' più ordinato:

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

ho fatto la mia propria funzione per il filtraggio di array 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; 
$$; 

lo uso come

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; 

ovviamente funziona solo in PostgreSQL 9.3. Ho anche uno simile per i campi di oggetto:

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: è possibile vedere alcuni utils (alcuni non sono originariamente miei ma erano prendere da altre soluzioni StackOverflow) qui al mio succo: https://gist.github.com/le-doude/8b0e89d71a32efd21283

1

Se questo è in realtà un bug PostgreSQL, spero sia stato corretto in 9.4. Molto noioso.

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; 

Personalmente non faccio il bit COALESCE, restituisco solo il NULL. La tua chiamata.

1

Ho usato this answer (mi dispiace, non riesco a collegarmi al tuo nome utente) ma credo di averlo migliorato un po '.

Per la versione matrice possiamo

  1. sbarazzarci della ridondanza doppia selezionare
  2. uso json_agg invece dei array_to_json(array_agg()) chiamate

e ottenere questo:

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

Per 9.3, per la versione dell'oggetto, possiamo:

  1. sbarazzarsi del WITH clausola di non utilizzato
  2. sbarazzarsi del superfluo doppio selezionare

e ottenere questo:

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

Per 9.4, non abbiamo utilizzare la roba di assemblaggio di stringhe per costruire l'oggetto, in quanto possiamo utilizzare il nuovo aggiunto 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 <> '""'; 
$$; 
22

In 9.4 è possibile utilizzare la coalesce e un'espressione di filtro aggregata.

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'espressione di filtro impedisce l'aggregato di elaborare le righe che sono nulle perché la sinistra join condizione non è soddisfatta, così si finisce con un nullo database invece del json [null]. Una volta che hai un database nullo, puoi usare il coalesce come al solito.

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

+1

Questo è fantastico! Funziona anche per 'json_object_agg': ' COALESCE (json_object_agg (prop.key, prop.value) FILTER (WHERE prop.key NON È NULL), '{}') :: json' – cansik