La consulta probablemente se puede simplificar a:
SELECT u.name AS user_name
, p.name AS project_name
, tl.created_on::date AS changeday
, coalesce(sum(nullif(new_value, '')::numeric), 0)
- coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
FROM users u
LEFT JOIN (
tasks t
JOIN fixins f ON f.id = t.fixin_id
JOIN projects p ON p.id = f.project_id
JOIN task_log_entries tl ON tl.task_id = t.id
AND tl.field_id = 18
AND (tl.created_on IS NULL OR
tl.created_on >= '2013-09-08' AND
tl.created_on < '2013-09-09') -- upper border!
) ON t.assignee_id = u.id
WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3;
Esto devuelve todos los usuarios que alguna vez han tenido alguna tarea.
Más datos por proyectos y día donde existen datos en el intervalo de fechas especificado en task_log_entries
.
Puntos principales
-
La función agregada
sum()
ignoraNULL
valores.COALESCE()
por fila ya no se requiere tan pronto como reformule el cálculo como la diferencia de dos sumas:,coalesce(sum(nullif(new_value, '')::numeric), 0) - coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
Sin embargo, si es posible que todos las columnas de una selección tienen
NULL
o cadenas vacías, envuelva las sumas enCOALESCE
una vez.
Estoy usandonumeric
en lugar defloat
, alternativa más segura para minimizar los errores de redondeo. -
Su intento de obtener valores distintos de la combinación de
users
ytasks
es inútil, ya que te unes atask
una vez más más abajo. Aplane toda la consulta para que sea más simple y rápida. -
Estos referencias posicionales son solo una conveniencia notacional:
GROUP BY 1, 2, 3 ORDER BY 1, 2, 3
... haciendo lo mismo que en su consulta original.
-
Para obtener una
date
de unatimestamp
simplemente puede transmitir adate
:tl.created_on::date AS changeday
Pero es mucho mejor probar con valores originales en
WHERE
cláusula oJOIN
condición (si es posible, y es posible aquí), para que Postgres pueda usar índices simples en la columna (si está disponible):AND (tl.created_on IS NULL OR tl.created_on >= '2013-09-08' AND tl.created_on < '2013-09-09') -- next day as excluded upper border
Tenga en cuenta que un literal de fecha se convierte en una
timestamp
a las00:00
del día en su hora actual zona . Tienes que elegir el siguiente día y excluir como borde superior. O proporcione un literal de marca de tiempo más explícito como'2013-09-22 0:0 +2':: timestamptz
. Más información sobre la exclusión del borde superior: -
Para el requisito
every user who has ever been assigned to a task
agrega elWHERE
cláusula:WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
-
Lo más importante :UN
LEFT [OUTER] JOIN
conserva todas las filas a la izquierda de la unión. Agregando unWHERE
cláusula a la derecha tabla puede anular este efecto. En su lugar, mueva la expresión de filtro aJOIN
cláusula. Más explicación aquí: -
Paréntesis se puede utilizar para forzar el orden en que se unen las tablas. Rara vez se necesita para consultas simples, pero es muy útil en este caso. Uso la función para unirme a
task
,fixins
,projects
ytask_log_entries
antes de unirlo todo ausers
- sin subconsulta. -
Alias de tabla facilita la escritura de consultas complejas.