PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Plusieurs appels array_agg() dans une seule requête

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.