DISTINCT
viene spesso applicato per riparare le query che sono marce dall'interno e che sono spesso lente e/o errate. Non moltiplicare le righe per cominciare, quindi non devi eliminare i duplicati indesiderati alla fine.
L'unione a più n-tabelle ("ha molti") contemporaneamente moltiplica le righe nel set di risultati. È come un CROSS JOIN
o prodotto cartesiano per procura :
- Due SQL LEFT JOINS producono un risultato errato
Esistono vari modi per evitare questo errore.
Prima aggrega, poi unisciti
Tecnicamente, la query funziona finché ti unisci a uno tabella con più righe alla volta prima di aggregare:
SELECT e.id, e.name, e.age, e.streets, arrag_agg(wd.day) AS days
FROM (
SELECT e.id, e.name, e.age, array_agg(ad.street) AS streets
FROM employees e
JOIN address ad ON ad.employeeid = e.id
GROUP BY e.id -- id enough if it is defined PK
) e
JOIN workingdays wd ON wd.employeeid = e.id
GROUP BY e.id, e.name, e.age;
È anche meglio includere la chiave primaria id
e GROUP BY
it, perché name
e age
non sono necessariamente unici. Potresti unire due dipendenti per errore.
Ma puoi aggregare in una sottoquery prima ti iscrivi, è superiore a meno che tu non abbia WHERE
selettivo condizioni su employees
:
SELECT e.id, e.name, e.age, ad.streets, arrag_agg(wd.day) AS days
FROM employees e
JOIN (
SELECT employeeid, array_agg(ad.street) AS streets
FROM address
GROUP BY 1
) ad ON ad.employeeid = e.id
JOIN workingdays wd ON e.id = wd.employeeid
GROUP BY e.id, e.name, e.age, ad.streets;
Oppure aggrega entrambi:
SELECT name, age, ad.streets, wd.days
FROM employees e
JOIN (
SELECT employeeid, array_agg(ad.street) AS streets
FROM address
GROUP BY 1
) ad ON ad.employeeid = e.id
JOIN (
SELECT employeeid, arrag_agg(wd.day) AS days
FROM workingdays
GROUP BY 1
) wd ON wd.employeeid = e.id;
L'ultimo è in genere più veloce se recuperi tutto o quasi delle righe nelle tabelle di base.
Nota che usando JOIN
e non LEFT JOIN
rimuove dal risultato i dipendenti che non hanno indirizzo o nessun giorno lavorativo. Questo può o non può essere inteso. Passa a LEFT JOIN
per conservare tutto dipendenti nel risultato.
Subquery correlate/unione LATERALE
Per una piccola selezione , considererei invece le subquery correlate:
SELECT name, age
, (SELECT array_agg(street) FROM address WHERE employeeid = e.id) AS streets
, (SELECT arrag_agg(day) FROM workingdays WHERE employeeid = e.id) AS days
FROM employees e
WHERE e.namer = 'peter'; -- very selective
Oppure, con Postgres 9.3 o versioni successive, puoi utilizzare LATERAL
si unisce per questo:
SELECT e.name, e.age, a.streets, w.days
FROM employees e
LEFT JOIN LATERAL (
SELECT array_agg(street) AS streets
FROM address
WHERE employeeid = e.id
GROUP BY 1
) a ON true
LEFT JOIN LATERAL (
SELECT array_agg(day) AS days
FROM workingdays
WHERE employeeid = e.id
GROUP BY 1
) w ON true
WHERE e.name = 'peter'; -- very selective
- Qual è la differenza tra LATERAL e una sottoquery in PostgreSQL?
Entrambe le query conservano tutto dipendenti nel risultato.