PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Junção externa esquerda agindo como junção interna


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 agregada sum() ignora NULL 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êm NULL ou strings vazias, envolva as somas em COALESCE uma vez.
    Estou usando numeric em vez de float , alternativa mais segura para minimizar erros de arredondamento.

  • Sua tentativa de obter valores distintos da junção de users e tasks é inútil, já que você se junta a task 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 uma date de um timestamp você pode simplesmente transmitir para date :
    tl.created_on::date AS changeday
    

    Mas é muito melhor testar com valores originais no WHERE cláusula ou JOIN 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 um timestamp às 00: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 requisito every user who has ever been assigned to a task adicione o WHERE cláusula:
    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    

  • O mais importante :A LEFT [OUTER] JOIN preserva todas as linhas à esquerda da junção. Adicionando um WHERE cláusula sobre o direito tabela pode anular este efeito. Em vez disso, mova a expressão de filtro para JOIN 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 da task , fixins , projects e task_log_entries antes de juntar tudo à esquerda para users - sem subconsulta.

  • Aliases de tabela tornar a escrita de consultas complexas mais fácil.