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()ignoraNULLvalores.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 hoursSin embargo, si es posible que todos las columnas de una selección tienen
NULLo cadenas vacías, envuelva las sumas enCOALESCEuna vez.
Estoy usandonumericen lugar defloat, alternativa más segura para minimizar los errores de redondeo. -
Su intento de obtener valores distintos de la combinación de
usersytaskses inútil, ya que te unes ataskuna 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
datede unatimestampsimplemente puede transmitir adate:tl.created_on::date AS changedayPero es mucho mejor probar con valores originales en
WHEREcláusula oJOINcondició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 borderTenga en cuenta que un literal de fecha se convierte en una
timestampa las00:00del 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 taskagrega elWHEREcláusula:WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id) -
Lo más importante :UN
LEFT [OUTER] JOINconserva todas las filas a la izquierda de la unión. Agregando unWHEREcláusula a la derecha tabla puede anular este efecto. En su lugar, mueva la expresión de filtro aJOINclá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,projectsytask_log_entriesantes de unirlo todo ausers- sin subconsulta. -
Alias de tabla facilita la escritura de consultas complejas.