sql >> Database >  >> RDS >> PostgreSQL

Linker buitenste join gedraagt ​​zich als inner join

De zoekopdracht kan waarschijnlijk worden vereenvoudigd tot:

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;

Dit geeft alle gebruikers terug die ooit een taak hebben gehad.
Plus gegevens per projecten en dag waar gegevens bestaan ​​in het opgegeven datumbereik in task_log_entries .

Belangrijkste punten

  • De aggregaatfunctie sum() negeert NULL waarden. COALESCE() per rij is niet meer nodig zodra u de berekening herschikt als het verschil van twee sommen:

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

    Echter, als het is mogelijk dat alle kolommen van een selectie hebben NULL of lege strings, wikkel de sommen in COALESCE eenmaal.
    Ik gebruik numeric in plaats van float , veiliger alternatief om afrondingsfouten te minimaliseren.

  • Uw poging om verschillende waarden te verkrijgen uit de samenvoeging van users en tasks is zinloos, aangezien je meedoet aan task nog een keer verder naar beneden. Maak de hele query plat om het eenvoudiger en sneller te maken.

  • Deze positionele referenties zijn slechts een notatie gemak:

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

    ... hetzelfde te doen als in uw oorspronkelijke zoekopdracht.

  • Om een ​​date te krijgen van een timestamp je kunt gewoon casten naar date :

    tl.created_on::date AS changeday
    

    Maar het is veel beter om te testen met originele waarden in de WHERE clausule of JOIN voorwaarde (indien mogelijk, en het is hier mogelijk), zodat Postgres gewone indices op de kolom kan gebruiken (indien beschikbaar):

     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
    

    Merk op dat een letterlijke datum wordt geconverteerd naar een timestamp om 00:00 van de dag op jouw huidige tijd zone . Je moet de volgende . kiezen dag en uitsluiten het als bovenrand. Of geef een meer expliciete letterlijke tijdstempel, zoals '2013-09-22 0:0 +2':: timestamptz . Meer over het uitsluiten van de bovenrand:

  • Voor de vereiste every user who has ever been assigned to a task voeg de WHERE . toe clausule:

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Het belangrijkste :EEN LEFT [OUTER] JOIN behoudt alle rijen links van de join. Een WHERE toevoegen clausule aan de rechter tabel kan dit effect teniet doen. In plaats daarvan verplaats de filterexpressie naar de JOIN clausule. Meer uitleg hier:

  • Haakjes kan worden gebruikt om de volgorde waarin tabellen worden samengevoegd te forceren. Zelden nodig voor eenvoudige vragen, maar in dit geval erg handig. Ik gebruik de functie om deel te nemen aan task , fixins , projects en task_log_entries voordat je alles samenvoegt met users - zonder subquery.

  • Tabelaliassen maak het schrijven van complexe query's gemakkelijker.



  1. Configureer SQL*Plus om niets anders dan gegevens terug te geven

  2. De invoer van een kolom in postgres opschonen

  3. Hoe converteer ik BLOB naar VARCHAR in MySQL?

  4. Hoe de vervolgkeuzelijst te vernieuwen zonder paginavernieuwing?