PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Join esterno sinistro che agisce come join interno

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() ignora NULL 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 in COALESCE una volta.
    Sto usando numeric invece di float , un'alternativa più sicura per ridurre al minimo gli errori di arrotondamento.

  • Il tuo tentativo di ottenere valori distinti dall'unione di users e tasks è inutile, dal momento che ti unisci a task 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 un timestamp puoi semplicemente trasmettere a date :

    tl.created_on::date AS changeday
    

    Ma è molto meglio testare con i valori originali in WHERE clausola o JOIN 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 alle 00: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 il WHERE 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 un WHERE clausola sul diritto tabella può annullare questo effetto. Invece, sposta l'espressione del filtro in JOIN 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 e task_log_entries prima di unire tutto a sinistra a users - senza subquery.

  • Alias ​​di tabella semplificare la scrittura di query complesse.