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.