PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Left Outer Join verhält sich wie Inner Join

Die Abfrage kann wahrscheinlich vereinfacht werden zu:

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;

Dies gibt alle Benutzer zurück, die jemals eine Aufgabe hatten.
Plus Daten pro Projekt und Tag wobei Daten im angegebenen Datumsbereich in task_log_entries vorhanden sind .

Wichtige Punkte

  • Die Aggregatfunktion sum() ignoriert NULL Werte. COALESCE() pro Zeile wird nicht mehr benötigt, sobald Sie die Berechnung als Differenz zweier Summen umformen:

     ,coalesce(sum(nullif(new_value, '')::numeric), 0) -
      coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
    

    Allerdings wenn es ist möglich, dass alle Spalten einer Auswahl haben NULL oder leere Zeichenfolgen, packen Sie die Summen in COALESCE einmal.
    Ich verwende numeric statt float , sicherere Alternative zur Minimierung von Rundungsfehlern.

  • Ihr Versuch, eindeutige Werte aus dem Beitritt von users zu erhalten und tasks ist zwecklos, da Sie task beitreten noch einmal weiter unten. Reduzieren Sie die gesamte Abfrage, um sie einfacher und schneller zu machen.

  • Diese Positionsreferenzen sind nur eine Notationshilfe:

    GROUP BY 1, 2, 3
    ORDER BY 1, 2, 3
    

    ... das Gleiche tun wie in Ihrer ursprünglichen Anfrage.

  • Um ein date zu erhalten von einem timestamp Sie können einfach auf date umwandeln :

    tl.created_on::date AS changeday
    

    Aber es ist viel besser, mit Originalwerten in WHERE zu testen -Klausel oder JOIN Bedingung (falls möglich, und es ist hier möglich), sodass Postgres einfache Indizes für die Spalte verwenden kann (falls verfügbar):

     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
    

    Beachten Sie, dass ein Datumsliteral wird in einen timestamp umgewandelt um 00:00 des Tages zu Ihrer aktuellen Zeit Zone . Sie müssen Weiter auswählen Tag und ausschließen es als obere Grenze. Oder geben Sie ein expliziteres Zeitstempel-Literal wie '2013-09-22 0:0 +2':: timestamptz an . Mehr zum Ausschließen des oberen Randes:

  • Für die Anforderung every user who has ever been assigned to a task füge das WHERE hinzu Klausel:

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Am wichtigsten :Ein LEFT [OUTER] JOIN behält alle Zeilen links vom Join bei. Hinzufügen eines WHERE Klausel auf der Rechts Tabelle kann diesen Effekt aufheben. Verschieben Sie stattdessen den Filterausdruck in JOIN Klausel. Mehr Erklärung hier:

  • Klammern kann verwendet werden, um die Reihenfolge zu erzwingen, in der Tabellen verknüpft werden. Wird selten für einfache Abfragen benötigt, ist in diesem Fall jedoch sehr nützlich. Ich verwende die Funktion, um task beizutreten , fixins , projects und task_log_entries bevor Sie alles mit users verbinden - ohne Unterabfrage.

  • Tabellenaliase das Schreiben komplexer Abfragen vereinfachen.