PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Mehrere Aufrufe von array_agg() in einer einzigen Abfrage

DISTINCT wird oft angewendet, um Abfragen zu reparieren, die von innen faul sind, und das ist oft langsam und / oder falsch. Vervielfachen Sie die Zeilen nicht von Anfang an, dann müssen Sie am Ende keine unerwünschten Duplikate aussortieren.

Das gleichzeitige Verbinden mit mehreren n-Tabellen ("hat viele") multipliziert die Zeilen in der Ergebnismenge. Das ist wie ein CROSS JOIN oder kartesisches Produkt per Proxy :

  • Zwei SQL LEFT JOINS erzeugen ein falsches Ergebnis

Es gibt verschiedene Möglichkeiten, diesen Fehler zu vermeiden.

Erst aggregieren, später beitreten

Technisch gesehen funktioniert die Abfrage, solange Sie one beitreten Tabelle mit mehreren Zeilen gleichzeitig, bevor Sie aggregieren:

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;

Am besten ist es auch, den Primärschlüssel id einzuschließen und GROUP BY es, weil name und age sind nicht unbedingt einzigartig. Sie könnten versehentlich zwei Mitarbeiter zusammenführen.

Aber Sie können in einer Unterabfrage vorher aggregieren Sie beitreten, das ist besser, es sei denn, Sie haben ein selektives WHERE Bedingungen für 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;

Oder aggregieren Sie beides:

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;

Letzteres ist normalerweise schneller, wenn Sie alle oder die meisten abrufen der Zeilen in den Basistabellen.

Beachten Sie, dass Sie JOIN verwenden und nicht LEFT JOIN entfernt Mitarbeiter aus dem Ergebnis, die keine Adresse oder haben keine Werktage. Das kann beabsichtigt sein oder auch nicht. Wechseln Sie zu LEFT JOIN um alle zu behalten Mitarbeiter im Ergebnis.

Korrelierte Unterabfragen / LATERAL Join

Für eine kleine Auswahl , würde ich stattdessen korrelierte Unterabfragen in Betracht ziehen:

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

Oder Sie können mit Postgres 9.3 oder höher LATERAL verwenden schließt sich dafür an:

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
  • Was ist der Unterschied zwischen LATERAL und einer Unterabfrage in PostgreSQL?

Jede Abfrage behält all Mitarbeiter im Ergebnis.