sql >> Databasteknik >  >> RDS >> PostgreSQL

Vänster yttre fog fungerar som inre fog

Frågan kan förmodligen förenklas till:

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;

Detta returnerar alla användare som någonsin har haft någon uppgift.
Plus data per projekt och dag där data finns inom det angivna datumintervallet i task_log_entries .

Huvudpunkter

  • aggregationsfunktionen sum() ignorerar NULL värden. COALESCE() per rad krävs inte längre så snart du omarbetar beräkningen som skillnaden mellan två summor:

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

    Men om det är möjligt att alla kolumner i ett urval har NULL eller tomma strängar, slå in summorna i COALESCE en gång.
    Jag använder numeric istället för float , säkrare alternativ för att minimera avrundningsfel.

  • Ditt försök att få distinkta värden från sammanfogningen av users och tasks är meningslöst eftersom du går med i task ännu en gång längre ner. Platta ut hela frågan för att göra det enklare och snabbare.

  • Dessa positionsreferenser är bara en notationsbekvämlighet:

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

    ... gör samma sak som i din ursprungliga fråga.

  • För att få ett date från en timestamp du kan helt enkelt casta till date :

    tl.created_on::date AS changeday
    

    Men det är mycket bättre att testa med originalvärden i WHERE klausul eller JOIN skick (om möjligt, och det är möjligt här), så Postgres kan använda vanliga index på kolumnen (om tillgängligt):

     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
    

    Observera att en datum bokstavlig konverteras till en timestamp vid 00:00 för dagen vid din nuvarande tid zon . Du måste välja nästa dag och uteslut det som övre kant. Eller ange en mer explicit tidsstämpel som '2013-09-22 0:0 +2':: timestamptz . Mer om att exkludera övre kant:

  • För kravet every user who has ever been assigned to a task lägg till WHERE klausul:

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Det viktigaste :En LEFT [OUTER] JOIN bevarar alla rader till vänster om sammanfogningen. Lägga till en WHERE klausul till höger tabell kan ogiltigförklara denna effekt. Flytta istället filteruttrycket till JOIN klausul. Mer förklaring här:

  • Parentes kan användas för att tvinga fram ordningen i vilken tabeller sammanfogas. Behövs sällan för enkla frågor, men mycket användbart i det här fallet. Jag använder funktionen för att gå med i task , fixins , projects och task_log_entries innan du lämnade sammanfogning med allt till users - utan underfråga.

  • Tabellalias gör det lättare att skriva komplexa frågor.



  1. MYSQL - Flatten Table Query

  2. Problem med utf-8-kodning med PHP + MySQL

  3. UPPDATERA/INSERT baserat på om en rad finns

  4. Få kolumnsumma och använd för att beräkna procent av totalen (mySQL)