A consulta provavelmente pode ser simplificada para:
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;
Isso retorna todos os usuários que já tiveram alguma tarefa.
Mais dados por projeto e dia onde os dados existem no intervalo de datas especificado em
task_log_entries
. Pontos principais
-
A função agregadasum()
ignoraNULL
valores.COALESCE()
por linha não é mais necessário assim que você reformular o cálculo como a diferença de duas somas:
,coalesce(sum(nullif(new_value, '')::numeric), 0) - coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
No entanto, se é possível que todos as colunas de uma seleção têmNULL
ou strings vazias, envolva as somas emCOALESCE
uma vez.
Estou usandonumeric
em vez defloat
, alternativa mais segura para minimizar erros de arredondamento.
-
Sua tentativa de obter valores distintos da junção deusers
etasks
é inútil, já que você se junta atask
mais uma vez mais abaixo. Aplaine toda a consulta para torná-la mais simples e rápida.
-
Estes referências posicionais são apenas uma conveniência de notação:
GROUP BY 1, 2, 3 ORDER BY 1, 2, 3
... fazendo o mesmo que na sua consulta original.
-
Para obter umadate
de umtimestamp
você pode simplesmente transmitir paradate
:
tl.created_on::date AS changeday
Mas é muito melhor testar com valores originais noWHERE
cláusula ouJOIN
condição (se possível, e é possível aqui), então o Postgres pode usar índices simples na coluna (se disponível):
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
Observe que um literal de data é convertido para umtimestamp
às00:00
do dia no seu horário atual zona . Você precisa escolher o próximo dia e excluir -lo como borda superior. Ou forneça um literal de carimbo de data/hora mais explícito, como'2013-09-22 0:0 +2':: timestamptz
. Mais sobre como excluir a borda superior:
-
Para o requisitoevery user who has ever been assigned to a task
adicione oWHERE
cláusula:
WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
-
O mais importante :ALEFT [OUTER] JOIN
preserva todas as linhas à esquerda da junção. Adicionando umWHERE
cláusula sobre o direito tabela pode anular este efeito. Em vez disso, mova a expressão de filtro paraJOIN
cláusula. Mais explicação aqui:
-
Parênteses pode ser usado para forçar a ordem na qual as tabelas são unidas. Raramente necessário para consultas simples, mas muito útil neste caso. Eu uso o recurso para participar datask
,fixins
,projects
etask_log_entries
antes de juntar tudo à esquerda parausers
- sem subconsulta.
-
Aliases de tabela tornar a escrita de consultas complexas mais fácil.