DISTINCT
est souvent appliqué pour réparer des requêtes qui sont pourries de l'intérieur, et qui sont souvent lentes et/ou incorrectes. Ne multipliez pas les lignes pour commencer, vous n'aurez pas à trier les doublons indésirables à la fin.
Joindre plusieurs n-tables ("a plusieurs") à la fois multiplie les lignes dans le jeu de résultats. C'est comme un CROSS JOIN
ou produit cartésien par procuration :
- Deux SQL LEFT JOINS produisent un résultat incorrect
Il existe plusieurs façons d'éviter cette erreur.
Agrégez d'abord, rejoignez plus tard
Techniquement, la requête fonctionne tant que vous rejoignez une table avec plusieurs lignes à la fois avant d'agréger :
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;
Il est également préférable d'inclure la clé primaire id
et GROUP BY
parce que name
et age
ne sont pas nécessairement uniques. Vous pourriez fusionner deux employés par erreur.
Mais vous pouvez agréger dans une sous-requête avant vous rejoignez, c'est supérieur à moins que vous n'ayez un WHERE
sélectif conditions sur les 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;
Ou agréger les deux :
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;
Le dernier est généralement plus rapide si vous récupérez tout ou la plupart des lignes dans les tables de base.
Notez que l'utilisation de JOIN
et non LEFT JOIN
supprime les employés du résultat qui n'ont pas d'adresse ou pas de jours ouvrés. Cela peut être intentionnel ou non. Passer à LEFT JOIN
pour conserver tous employés dans le résultat.
Sous-requêtes corrélées / jointure LATERAL
Pour une petite sélection , je considérerais plutôt les sous-requêtes corrélées :
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
Ou, avec Postgres 9.3 ou version ultérieure, vous pouvez utiliser LATERAL
rejoint pour cela :
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
- Quelle est la différence entre LATERAL et une sous-requête dans PostgreSQL ?
L'une ou l'autre requête conserve tout employés dans le résultat.