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.