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()ignoreNULLvaleurs.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 hoursCependant, si il est possible que tous les colonnes d'une sélection ont
NULLou des chaînes vides, enveloppez les sommes dansCOALESCEune fois.
J'utilisenumericau 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
usersettasksest futile, puisque vous vous joignez àtaskencore 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'untimestampvous pouvez simplement caster endate:tl.created_on::date AS changedayMais il est préférable de tester avec les valeurs d'origine dans le
WHEREclause ouJOINcondition (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 borderNotez qu'un littéral de date est converti en un
timestampà00:00du 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 taskajouter leWHEREclause :WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id) -
Le plus important :Une
LEFT [OUTER] JOINconserve toutes les lignes à gauche de la jointure. Ajout d'unWHEREclause sur le droit table peut annuler cet effet. Au lieu de cela, déplacez l'expression de filtre vers leJOINclause. 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,projectsettask_log_entriesavant de tout joindre à gauche auxusers- sans sous-requête. -
Alias de table faciliter la rédaction de requêtes complexes.