2015-06-29 31 views
20

Sto selezionando alcuni oggetti e i relativi tag in Postgres. Lo schema è abbastanza semplice, tre tabelle:Postgres restituisce [null] invece di [] per array_agg della tabella join

oggettiid

marcatureid | object_id | tag_id

tagid | tag

sto unendo i tavoli di questo tipo, utilizzando array_agg per aggregare i tag in un campo:

SELECT objects.*, 
    array_agg(tags.tag) AS tags, 
FROM objects 
LEFT JOIN taggings ON objects.id = taggings.object_id 
LEFT JOIN tags ON tags.id = taggings.tag_id 

Tuttavia, se l'oggetto non ha tag, Postgres restituisce questo:

[ null ] 

invece di un un array vuoto. Come posso restituire un array vuoto quando non ci sono tag? Ho ricontrollato che non ho restituito un tag nullo.

Il aggregate docs dice "La funzione di coalesce può essere utilizzata per sostituire zero o un array vuoto per null quando necessario". Ho provato COALESCE(ARRAY_AGG(tags.tag)) as tags ma restituisce ancora una matrice con null. Ho provato a fare il secondo parametro numerose cose (come COALESCE(ARRAY_AGG(tags.tag), ARRAY()), ma tutte causano errori di sintassi

risposta

17

Un'altra opzione potrebbe essere array_remove(..., NULL) (introduced in 9.3) se tags.tag è NOT NULL (altrimenti si potrebbe desiderare di mantenere NULL valori nella matrice, ma in quel caso, non è possibile distinguere tra un singolo tag NULL esistente e un tag NULL a causa dello LEFT JOIN):

SELECT objects.*, 
    array_remove(array_agg(tags.tag), NULL) AS tags, 
FROM objects 
LEFT JOIN taggings ON objects.id = taggings.object_id 
LEFT JOIN tags ON tags.id = taggings.tag_id 

Se non vengono trovati tag, viene restituito un array vuoto.

+0

Ho scelto questa risposta, forse ingiustamente agli altri, perché comporta una modifica delle query molto inferiore e non mi interessa il caso del tag null qui. –

+0

Questa è la risposta che ho usato anch'io, ma per coloro che desiderano saperne di più sul "perché", vedere la risposta di Patrick sotto insieme alla documentazione della funzione aggregata https://www.postgresql.org/docs/9.5/static/functions- aggregate.html –

1

La documentazione dice che viene restituito un array contenente NULL. Se si desidera convertirlo in un array vuoto, allora si bisogno di fare qualche magia minore:.

SELECT objects.id, 
    CASE WHEN length((array_agg(tags.tag))[1]) > 0 
    THEN array_agg(tags.tag) 
    ELSE ARRAY[]::text[] END AS tags 
FROM objects 
LEFT JOIN taggings ON objects.id = taggings.object_id 
LEFT JOIN tags ON tags.id = taggings.tag_id 
GROUP BY 1; 

Questo presuppone che i tag sono di text tipo (o una delle sue varianti), modificare il cast come richiesto

il trucco è che il primo (e solo) l'elemento in un array [NULL] ha una lunghezza pari a 0, quindi se qualsiasi dato viene restituito da tags si restituisce l'aggregato, altrimenti si costruisce un array vuoto del tipo corretto.

Per inciso, la dichiarazione nella documentazione sull'utilizzo coalesce() è un po 'scadente: ciò che si intende è che se non si desidera NULL di conseguenza, è possibile utilizzare coalesce() per trasformarla in un 0 o qualche altra uscita del vostro scelta. Ma è necessario applicarlo agli elementi dell'array invece dell'array, che nel tuo caso non fornirebbe una soluzione.

+1

Sì, in realtà si vuole il contrario di 'coalesce',' nullif', se la domanda è in realtà come appare. –

+0

'length (NULL)' è 'NULL', non' 0'. 'length (NULL)> 0' è anche' NULL', che capita di cadere nel caso 'ELSE'. Ma così 'length ('')> 0', e non penso che questo sia il comportamento desiderato. –

+0

https://www.postgresql.org/docs/9.5/static/functions-aggregate.html È un po 'sepolto nella documentazione, ma il testo rilevante è "Va notato che, a parte il conteggio, queste funzioni restituiscono un valore nullo quando nessuna riga è selezionata, in particolare la somma di nessuna riga restituisce null, non zero come ci si potrebbe aspettare e array_agg restituisce null anziché una matrice vuota quando non ci sono righe di input. " –

4

I documenti dicono che quando si aggregano zero righe, si ottiene un valore nullo e la nota sull'utilizzo di COALESCE si riferisce a questo caso specifico.

Ciò non vale per la ricerca, per il modo a LEFT JOIN comporta - quando trova nullo righe corrispondenti, restituisce una fila, riempito con null (e l'aggregato di una riga nulla è un array con un elemento nullo).

si potrebbe essere tentati di sostituire alla cieca [NULL] con [] in uscita, ma poi si perde la capacità di distiguish tra oggetti senza tag e tagged oggetti in cui tags.tag è nullo. La logica dell'applicazione e/o i vincoli di integrità potrebbero non consentire questo secondo caso, ma è una ragione in più per non sopprimere un tag nullo se riesce ad intrufolarsi.

È possibile identificare un oggetto senza tag (o in in generale, indica quando un LEFT JOIN non trova corrispondenze) controllando se il campo sull'altro lato della condizione di join è nullo. Quindi nel tuo caso, basta sostituire

array_agg(tags.tag) 

con

CASE 
    WHEN taggings.object_id IS NULL 
    THEN ARRAY[]::text[] 
    ELSE array_agg(tags.tag) 
END 
+1

Penso che questa sia una spiegazione e una risposta migliori, tranne notare che richiede 'taggings.object_id' da aggiungere a una clausola' GROUP BY' per evitare l'errore di sintassi 'ERRORE: 42803: colonna" taggings.object_id "deve apparire nella clausola GROUP BY o essere usato in una funzione aggregata' - l'aggiunta di questa clausola altera i risultati finali? – user9645

+1

@ user9645: Supponendo che la query originale abbia un 'GROUP BY objects.id' (necessario per evitare questo stesso errore), cambiandola in' GROUP BY objects.id, taggings.object_id' non influenzerà il raggruppamento (la condizione 'JOIN' assicura che un dato valore' objects.id' non possa mai essere associato a più valori distinti 'taggings.object_id'). –

+0

Nick - Grazie l'ho pensato ma non è stato positivo. – user9645

1

ho scoperto che questo lo farà:

COALESCE(ARRAY_AGG(tags.tag), ARRAY[]::TEXT[]) 

... assumendo che tags.tag è un tipo di testo.

Non so se questo potrebbe non funzionare nelle versioni di Postgres precedenti, ma lo sto usando in ver. 9.6 e sembra funzionare e meno ingombrante della soluzione CASE WHEN x IS NULL... GROUP BY... fornita in precedenza.

+0

È stata più facile trovare questa risposta che non capirlo dai documenti – boatcoder

+0

Questo non fa nulla per me in 9.6, restituisce comunque '{NULL}' se non ci sono valori (in questo caso i tag). – Inkling

3

Dal 9.4 si può limitare una chiamata di funzione di aggregazione per procedere solo le righe che corrispondono a un certo criterio: array_agg(tags.tag) filter (where tags.tag is not null)