PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Várias chamadas array_agg() em uma única consulta


DISTINCT é frequentemente aplicado para reparar consultas que estão podres por dentro, e isso geralmente é lento e / ou incorreto. Não multiplique linhas para começar, então você não precisa separar duplicatas indesejadas no final.

Unir-se a várias n-tables ("tem muitas") de uma vez multiplica as linhas no conjunto de resultados. Isso é como um CROSS JOIN ou produto cartesiano por procuração :
  • Dois SQL LEFT JOINS produzem resultado incorreto

Existem várias maneiras de evitar esse erro.

Agregue primeiro, junte-se depois


Tecnicamente, a consulta funciona desde que você participe de um tabela com várias linhas por vez antes de agregar:
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;

Também é melhor incluir a chave primária id e GROUP BY porque name e age não são necessariamente únicos. Você pode mesclar dois funcionários por engano.

Mas você pode agregar em uma subconsulta antes você entra, isso é superior, a menos que você tenha seletivo WHERE condições para 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 agregue ambos:
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;

O último normalmente é mais rápido se você recuperar todos ou a maioria das linhas nas tabelas base.

Observe que usar JOIN e não LEFT JOIN remove do resultado os funcionários que não têm endereço ou sem dias úteis. Isso pode ou não ser intencional. Mude para LEFT JOIN para reter todos funcionários no resultado.

Subconsultas correlacionadas/junção LATERAL


Para uma pequena seleção , eu consideraria subconsultas correlacionadas:
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, com o Postgres 9.3 ou posterior, você pode usar LATERAL junta para isso:
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
  • Qual ​​é a diferença entre LATERAL e uma subconsulta no PostgreSQL?

Qualquer uma das consultas retém todos funcionários no resultado.