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

Venstre ydre led fungerer som indre led

Forespørgslen kan sandsynligvis forenkles til:

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;

Dette returnerer alle brugere, der nogensinde har haft en opgave.
Plus data pr. projekt og dag hvor data findes i det angivne datointerval i task_log_entries .

Vigtige punkter

  • aggregerfunktionen sum() ignorerer NULL værdier. COALESCE() pr. række er ikke påkrævet mere, så snart du omformulerer beregningen som forskellen på to summer:

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

    Men hvis det er muligt, at alle kolonner i et udvalg har NULL eller tomme strenge, pak summene ind i COALESCE én gang.
    Jeg bruger numeric i stedet for float , sikrere alternativ til at minimere afrundingsfejl.

  • Dit forsøg på at få forskellige værdier fra sammenføjningen af ​​users og tasks er nyttesløst, da du deltager i task endnu en gang længere nede. Gør hele forespørgslen flad for at gøre den enklere og hurtigere.

  • Disse positionsreferencer er blot en notationsbekvemmelighed:

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

    ... gør det samme som i din oprindelige forespørgsel.

  • For at få en date fra et timestamp du kan blot caste til date :

    tl.created_on::date AS changeday
    

    Men det er meget bedre at teste med originale værdier i WHERE klausul eller JOIN tilstand (hvis muligt, og det er muligt her), så Postgres kan bruge almindelige indekser på kolonnen (hvis tilgængelig):

     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
    

    Bemærk, at en dato bogstavelig konverteres til et timestamp kl. 00:00 dagens på dit nuværende tidspunkt zone . Du skal vælge den næste dag og ekskluder det som øvre grænse. Eller angiv et mere eksplicit tidsstempel, f.eks. '2013-09-22 0:0 +2':: timestamptz . Mere om udelukkelse af øvre kant:

  • For kravet every user who has ever been assigned to a task tilføj WHERE klausul:

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Vigtigst :En LEFT [OUTER] JOIN bevarer alle rækker til venstre for sammenføjningen. Tilføjelse af en WHERE klausul til højre tabel kan annullere denne effekt. Flyt i stedet filterudtrykket til JOIN klausul. Mere forklaring her:

  • Parentes kan bruges til at fremtvinge den rækkefølge, som tabeller samles i. Sjældent nødvendigt til simple forespørgsler, men meget nyttigt i dette tilfælde. Jeg bruger funktionen til at deltage i task , fixins , projects og task_log_entries før du forlader det hele til users - uden underforespørgsel.

  • Tabelaliasser gør det nemmere at skrive komplekse forespørgsler.



  1. AWS rds - Hvordan læser man fra en læst replika inde i en Java-applikation?

  2. Bedste måde at installere hstore på flere skemaer i en Postgres-database?

  3. SQL Server dynamisk PIVOT-forespørgsel?

  4. Automatisk dataindsamling af databaseskemaændringer i MS SQL Server