DISTINCT
wordt vaak toegepast om queries te repareren die van binnen verrot zijn, en dat is vaak traag en/of incorrect. Vermenigvuldig in het begin geen rijen, dan hoeft u aan het einde geen ongewenste duplicaten te sorteren.
Door samen te voegen met meerdere n-tabellen ("heeft er veel") tegelijk, worden rijen in de resultaatset vermenigvuldigd. Dat is zoiets als een CROSS JOIN
of Cartesiaans product bij volmacht :
- Twee SQL LEFT JOINS produceren een onjuist resultaat
Er zijn verschillende manieren om deze fout te voorkomen.
Eerst samenvoegen, later meedoen
Technisch gezien werkt de zoekopdracht zolang je lid wordt van one tabel met meerdere rijen tegelijk voordat u aggregeert:
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;
Het is ook het beste om de primaire sleutel id
. op te nemen en GROUP BY
het, omdat name
en age
zijn niet noodzakelijk uniek. U kunt per ongeluk twee medewerkers samenvoegen.
Maar u kunt vóór . aggregeren in een subquery je lid wordt, dat is superieur, tenzij je selectieve WHERE
. hebt voorwaarden voor 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;
Of aggregeer beide:
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;
De laatste is doorgaans sneller als u alle of de meeste ophaalt van de rijen in de basistabellen.
Merk op dat het gebruik van JOIN
en niet LEFT JOIN
verwijdert medewerkers uit het resultaat die geen adres hebben of geen werkdagen. Dat kan wel of niet de bedoeling zijn. Schakel over naar LEFT JOIN
om alle te behouden medewerkers in het resultaat.
Gecorreleerde subquery's / LATERAL join
Voor een kleine selectie , zou ik in plaats daarvan gecorreleerde subquery's overwegen:
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
Of, met Postgres 9.3 of hoger, kunt u LATERAL
. gebruiken sluit zich daarvoor aan:
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
- Wat is het verschil tussen LATERAL en een subquery in PostgreSQL?
Elke zoekopdracht behoudt alle medewerkers in het resultaat.