sql >> Databáze >  >> RDS >> PostgreSQL

Levé vnější spojení funguje jako vnitřní spojení

Dotaz lze pravděpodobně zjednodušit na:

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;

Tím se vrátí všichni uživatelé, kteří kdy měli nějaký úkol.
Plus data za projekty a den kde data existují v zadaném časovém období v task_log_entries .

Hlavní body

  • souhrnná funkce sum() ignoruje NULL hodnoty. COALESCE() na řádek již není vyžadován, jakmile předěláte výpočet jako rozdíl dvou součtů:

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

    Nicméně pokud je možné, že všechny sloupce výběru mají NULL nebo prázdné řetězce, zabalte součty do COALESCE jednou.
    Používám numeric místo float , bezpečnější alternativa pro minimalizaci zaokrouhlovacích chyb.

  • Váš pokus získat odlišné hodnoty ze spojení users a tasks je marné, protože se připojíte k task ještě jednou níž. Sjednoťte celý dotaz, aby byl jednodušší a rychlejší.

  • Tyto polohové odkazy jsou jen vymožeností zápisu:

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

    ... uděláte to samé jako v původním dotazu.

  • Chcete-li získat date z timestamp můžete jednoduše přenést na date :

    tl.created_on::date AS changeday
    

    Ale mnohem lepší je testovat s původními hodnotami v WHERE klauzule nebo JOIN stav (pokud je to možné a je to možné zde), takže Postgres může na sloupci použít prosté indexy (pokud jsou k dispozici):

     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
    

    Všimněte si, že datum literál se převede na timestamp v 00:00 dne ve vašem aktuálním čase zóna . Musíte vybrat další den a vyloučit to jako horní hranice. Nebo poskytněte explicitnější literál časového razítka, například '2013-09-22 0:0 +2':: timestamptz . Více o vyloučení horního okraje:

  • Pro požadavek every user who has ever been assigned to a task přidejte WHERE klauzule:

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Nejdůležitější :LEFT [OUTER] JOIN zachová všechny řádky nalevo od spojení. Přidání WHERE doložka vpravo tabulka může tento efekt zrušit. Místo toho přesuňte výraz filtru do JOIN doložka. Více vysvětlení zde:

  • Závorky lze použít k vynucení pořadí, ve kterém jsou tabulky spojeny. Zřídka potřebné pro jednoduché dotazy, ale v tomto případě velmi užitečné. Tuto funkci používám k připojení k task , fixins , projects a task_log_entries před levým připojením všech k users - bez poddotazu.

  • Aliasy tabulek zjednodušit psaní složitých dotazů.



  1. Jak automaticky znovu zadat dotaz pomocí LoaderManager

  2. úlohy cronu nebo plánovač PHP

  3. MYSQL Zkrácena nesprávná hodnota DOUBLE

  4. Zpracování výjimek PL/SQL:nedělat nic (ignorovat výjimku)