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()
ignoraNULL
i 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 hours
Tuttavia, se è possibile che tutti le colonne di una selezione hanno
NULL
o stringhe vuote, avvolgi le somme inCOALESCE
una volta.
Sto usandonumeric
invece difloat
, un'alternativa più sicura per ridurre al minimo gli errori di arrotondamento. -
Il tuo tentativo di ottenere valori distinti dall'unione di
users
etasks
è inutile, dal momento che ti unisci atask
ancora 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
date
da untimestamp
puoi semplicemente trasmettere adate
:tl.created_on::date AS changeday
Ma è molto meglio testare con i valori originali in
WHERE
clausola oJOIN
condizione (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 border
Tieni presente che un valore letterale della data viene convertito in un
timestamp
alle00:00
del 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 task
aggiungi ilWHERE
clausola:WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
-
La cosa più importante :A
LEFT [OUTER] JOIN
conserva tutte le righe a sinistra del join. Aggiunta di unWHERE
clausola sul diritto tabella può annullare questo effetto. Invece, sposta l'espressione del filtro inJOIN
clausola. 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
,projects
etask_log_entries
prima di unire tutto a sinistra ausers
- senza subquery. -
Alias di tabella semplificare la scrittura di query complesse.