DISTINCT
bruges ofte til reparationsforespørgsler, der er rådne indefra, og det er ofte langsomt og/eller forkert. Lad være med at gange rækker til at begynde med, så behøver du ikke sortere uønskede dubletter fra til sidst.
Sammenføjning til flere n-tabeller ("har mange") på én gang multiplicerer rækker i resultatsættet. Det er ligesom en CROSS JOIN
eller kartesisk produkt ved proxy :
- To SQL LEFT JOINS giver et forkert resultat
Der er forskellige måder at undgå denne fejl på.
Samle først, tilmeld dig senere
Teknisk set virker forespørgslen, så længe du tilslutter dig one tabel med flere rækker ad gangen, før du samler:
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 er også bedst at inkludere den primære nøgle id
og GROUP BY
det, fordi name
og age
er ikke nødvendigvis unikke. Du kan ved en fejl slå to medarbejdere sammen.
Men du kan samle i en underforespørgsel før du tilmelder dig, det er overlegent, medmindre du har selektiv WHERE
betingelser for 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 samle begge dele:
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 sidste er typisk hurtigere, hvis du henter alle eller de fleste af rækkerne i basistabellerne.
Bemærk, at du bruger JOIN
og ikke LEFT JOIN
fjerner medarbejdere fra resultatet, som ikke har nogen adresse eller ingen arbejdsdage. Det er måske ikke meningen. Skift til LEFT JOIN
at beholde alle medarbejdere i resultatet.
Korrelerede underforespørgsler / LATERAL join
For et lille udvalg , ville jeg overveje korrelerede underforespørgsler i stedet for:
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 nyere kan du bruge LATERAL
slutter sig til 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
- Hvad er forskellen mellem LATERAL og en underforespørgsel i PostgreSQL?
Begge forespørgsler bevarer alle medarbejdere i resultatet.