PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Jointure externe gauche agissant comme une jointure interne

La requête peut probablement être simplifiée en :

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;

Cela renvoie tous les utilisateurs qui ont déjà eu une tâche.
Plus les données par projet et par jour où les données existent dans la plage de dates spécifiée dans task_log_entries .

Points majeurs

  • La fonction d'agrégation sum() ignore NULL valeurs. COALESCE() par ligne n'est plus nécessaire dès que vous reformulez le calcul en différence de deux sommes :

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

    Cependant, si il est possible que tous les colonnes d'une sélection ont NULL ou des chaînes vides, enveloppez les sommes dans COALESCE une fois.
    J'utilise numeric au lieu de float , une alternative plus sûre pour minimiser les erreurs d'arrondi.

  • Votre tentative d'obtenir des valeurs distinctes à partir de la jointure des users et tasks est futile, puisque vous vous joignez à task encore une fois plus bas. Aplatissez toute la requête pour la rendre plus simple et plus rapide.

  • Ces références de position ne sont qu'une commodité de notation :

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

    ... faisant la même chose que dans votre requête d'origine.

  • Pour obtenir une date à partir d'un timestamp vous pouvez simplement caster en date :

    tl.created_on::date AS changeday
    

    Mais il est préférable de tester avec les valeurs d'origine dans le WHERE clause ou JOIN condition (si possible, et c'est possible ici), afin que Postgres puisse utiliser des indices simples sur la colonne (si disponible) :

     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
    

    Notez qu'un littéral de date est converti en un timestamp à 00:00 du jour à votre heure actuelle secteur . Vous devez choisir le suivant jour et exclure comme bordure supérieure. Ou fournissez un littéral d'horodatage plus explicite comme '2013-09-22 0:0 +2':: timestamptz . En savoir plus sur l'exclusion de la bordure supérieure :

  • Pour l'exigence every user who has ever been assigned to a task ajouter le WHERE clause :

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Le plus important :Une LEFT [OUTER] JOIN conserve toutes les lignes à gauche de la jointure. Ajout d'un WHERE clause sur le droit table peut annuler cet effet. Au lieu de cela, déplacez l'expression de filtre vers le JOIN clause. Plus d'explications ici :

  • Parenthèses peut être utilisé pour forcer l'ordre dans lequel les tables sont jointes. Rarement nécessaire pour des requêtes simples, mais très utile dans ce cas. J'utilise la fonctionnalité pour rejoindre la task , fixins , projects et task_log_entries avant de tout joindre à gauche aux users - sans sous-requête.

  • Alias ​​de table faciliter la rédaction de requêtes complexes.