sql >> Database >  >> RDS >> PostgreSQL

Meerdere array_agg()-aanroepen in een enkele query

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.



  1. Een zeer beschikbare database voor Moodle bouwen met PostgreSQL

  2. ListView-besturing Drag-Drop Gebeurtenissen sorteren

  3. MySQL varchar-indexlengte

  4. Hoe kan ik het entiteitsraamwerk dwingen om identiteitskolommen in te voegen?