DISTINCT
används ofta för att reparera frågor som är ruttna från insidan, och det är ofta långsamt och/eller felaktigt. Multiplicera inte rader till att börja med, då behöver du inte sortera ut oönskade dubbletter i slutet.
Att gå med i flera n-tabeller ("har många") samtidigt multiplicerar rader i resultatuppsättningen. Det är som en CROSS JOIN
eller kartesisk produkt via proxy :
- Två SQL LEFT JOINS ger felaktigt resultat
Det finns olika sätt att undvika detta misstag.
Aggregera först, gå med senare
Tekniskt sett fungerar frågan så länge du går med i one tabell med flera rader åt gången innan du aggregerar:
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;
Det är också bäst att inkludera primärnyckeln id
och GROUP BY
det, eftersom name
och age
är inte nödvändigtvis unika. Du kan slå samman två anställda av misstag.
Men du kan samla i en underfråga före du går med, det är överlägset om du inte har selektiv WHERE
villkor 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;
Eller slå ihop båda:
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;
Den sista är vanligtvis snabbare om du hämtar alla eller de flesta av raderna i bastabellerna.
Observera att du använder JOIN
och inte LEFT JOIN
tar bort anställda från resultatet som inte har någon adress eller inga arbetsdagar. Det kanske är meningen eller inte. Byt till LEFT JOIN
för att behålla alla anställda i resultatet.
Korrelerade underfrågor / LATERAL join
För ett litet urval , skulle jag överväga korrelerade underfrågor istället:
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
Eller, med Postgres 9.3 eller senare, kan du använda LATERAL
går med för det:
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
- Vad är skillnaden mellan LATERAL och en underfråga i PostgreSQL?
Båda sökfrågorna behåller alla anställda i resultatet.