La requête peut probablement être simplifiée en :
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;
Cela renvoie tous les utilisateurs qui ont déjà eu une tâche.
Plus les données par projet et par jour où les données existent dans la plage de dates spécifiée dans task_log_entries
.
Points majeurs
-
La fonction d'agrégation
sum()
ignoreNULL
valeurs.COALESCE()
par ligne n'est plus nécessaire dès que vous reformulez le calcul en différence de deux sommes :,coalesce(sum(nullif(new_value, '')::numeric), 0) - coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
Cependant, si il est possible que tous les colonnes d'une sélection ont
NULL
ou des chaînes vides, enveloppez les sommes dansCOALESCE
une fois.
J'utilisenumeric
au lieu defloat
, une alternative plus sûre pour minimiser les erreurs d'arrondi. -
Votre tentative d'obtenir des valeurs distinctes à partir de la jointure des
users
ettasks
est futile, puisque vous vous joignez àtask
encore une fois plus bas. Aplatissez toute la requête pour la rendre plus simple et plus rapide. -
Ces références de position ne sont qu'une commodité de notation :
GROUP BY 1, 2, 3 ORDER BY 1, 2, 3
... faisant la même chose que dans votre requête d'origine.
-
Pour obtenir une
date
à partir d'untimestamp
vous pouvez simplement caster endate
:tl.created_on::date AS changeday
Mais il est préférable de tester avec les valeurs d'origine dans le
WHERE
clause ouJOIN
condition (si possible, et c'est possible ici), afin que Postgres puisse utiliser des indices simples sur la colonne (si 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
Notez qu'un littéral de date est converti en un
timestamp
à00:00
du jour à votre heure actuelle secteur . Vous devez choisir le suivant jour et exclure comme bordure supérieure. Ou fournissez un littéral d'horodatage plus explicite comme'2013-09-22 0:0 +2':: timestamptz
. En savoir plus sur l'exclusion de la bordure supérieure : -
Pour l'exigence
every user who has ever been assigned to a task
ajouter leWHERE
clause :WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
-
Le plus important :Une
LEFT [OUTER] JOIN
conserve toutes les lignes à gauche de la jointure. Ajout d'unWHERE
clause sur le droit table peut annuler cet effet. Au lieu de cela, déplacez l'expression de filtre vers leJOIN
clause. Plus d'explications ici : -
Parenthèses peut être utilisé pour forcer l'ordre dans lequel les tables sont jointes. Rarement nécessaire pour des requêtes simples, mais très utile dans ce cas. J'utilise la fonctionnalité pour rejoindre la
task
,fixins
,projects
ettask_log_entries
avant de tout joindre à gauche auxusers
- sans sous-requête. -
Alias de table faciliter la rédaction de requêtes complexes.