DISTINCT
a menudo se aplica para reparar consultas que están podridas desde el interior, y eso suele ser lento y/o incorrecto. No multiplique las filas para empezar, así no tendrá que clasificar los duplicados no deseados al final.
Unirse a varias tablas n ("tiene muchas") a la vez multiplica las filas en el conjunto de resultados. Eso es como un CROSS JOIN
o producto cartesiano por proxy :
- Dos SQL LEFT JOINS producen un resultado incorrecto
Hay varias formas de evitar este error.
Agregue primero, únase después
Técnicamente, la consulta funciona siempre que se una a uno tabla con varias filas a la 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;
También es mejor incluir la clave principal id
y GROUP BY
porque name
y age
no son necesariamente únicos. Podría fusionar dos empleados por error.
Pero puede agregar en una subconsulta before te unes, eso es superior a menos que tengas un WHERE
selectivo condiciones de 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;
O agrega 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;
El último suele ser más rápido si recupera todos o la mayoría de las filas en las tablas base.
Tenga en cuenta que usar JOIN
y no LEFT JOIN
elimina del resultado a los empleados que no tienen dirección o sin días laborables. Eso puede o no ser intencionado. Cambiar a LEFT JOIN
retener todas empleados en el resultado.
Subconsultas correlacionadas / unión LATERAL
Para una pequeña selección , consideraría subconsultas correlacionadas en su lugar:
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
O, con Postgres 9.3 o posterior, puede usar LATERAL
se une para eso:
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
- ¿Cuál es la diferencia entre LATERAL y una subconsulta en PostgreSQL?
Cualquiera de las consultas retiene todas empleados en el resultado.