La query può probabilmente essere semplificata in:
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;
In questo modo vengono restituiti tutti gli utenti che hanno svolto attività.
Più dati per progetti e giorno dove i dati esistono nell'intervallo di date specificato in task_log_entries .
Punti principali
-
La funzione aggregata
sum()ignoraNULLi valori.COALESCE()per riga non è più richiesto non appena si riformula il calcolo come differenza di due somme:,coalesce(sum(nullif(new_value, '')::numeric), 0) - coalesce(sum(nullif(old_value, '')::numeric), 0) AS hoursTuttavia, se è possibile che tutti le colonne di una selezione hanno
NULLo stringhe vuote, avvolgi le somme inCOALESCEuna volta.
Sto usandonumericinvece difloat, un'alternativa più sicura per ridurre al minimo gli errori di arrotondamento. -
Il tuo tentativo di ottenere valori distinti dall'unione di
usersetasksè inutile, dal momento che ti unisci ataskancora una volta più in basso. Appiattisci l'intera query per renderla più semplice e veloce. -
Questi riferimenti posizionali sono solo una comodità di notazione:
GROUP BY 1, 2, 3 ORDER BY 1, 2, 3... facendo lo stesso della tua query originale.
-
Per ottenere una
dateda untimestamppuoi semplicemente trasmettere adate:tl.created_on::date AS changedayMa è molto meglio testare con i valori originali in
WHEREclausola oJOINcondizione (se possibile, ed è possibile qui), quindi Postgres può utilizzare indici semplici sulla colonna (se disponibile):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 borderTieni presente che un valore letterale della data viene convertito in un
timestampalle00:00del giorno alla tua ora attuale zona . Devi scegliere il successivo giorno ed escludi esso come bordo superiore. Oppure fornisci un timestamp letterale più esplicito come'2013-09-22 0:0 +2':: timestamptz. Ulteriori informazioni sull'esclusione del bordo superiore: -
Per il requisito
every user who has ever been assigned to a taskaggiungi ilWHEREclausola:WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id) -
La cosa più importante :A
LEFT [OUTER] JOINconserva tutte le righe a sinistra del join. Aggiunta di unWHEREclausola sul diritto tabella può annullare questo effetto. Invece, sposta l'espressione del filtro inJOINclausola. Maggiori spiegazioni qui: -
parentesi può essere utilizzato per forzare l'ordine di unione delle tabelle. Raramente necessario per query semplici, ma molto utile in questo caso. Uso la funzione per partecipare a
tasks,fixins,projectsetask_log_entriesprima di unire tutto a sinistra ausers- senza subquery. -
Alias di tabella semplificare la scrittura di query complesse.