PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Più chiamate array_agg() in una singola query

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.