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

Comment inclure les données manquantes pour plusieurs regroupements dans la période ?

Sur la base de certaines hypothèses (ambiguïtés dans la question), je suggère :

SELECT upper(trim(t.full_name)) AS teacher
     , m.study_month
     , r.room_code              AS room
     , count(s.room_id)         AS study_count

FROM   teachers t
CROSS  JOIN generate_series(date_trunc('month', now() - interval '12 month')  -- 12!
                          , date_trunc('month', now())
                          , interval '1 month') m(study_month)
CROSS  JOIN rooms r
LEFT   JOIN (                                                  -- parentheses!
          studies s
   JOIN   teacher_contacts tc ON tc.id = s.teacher_contact_id  -- INNER JOIN!
   ) ON tc.teacher_id = t.id
    AND s.study_dt >= m.study_month
    AND s.study_dt <  m.study_month + interval '1 month'      -- sargable!
    AND s.room_id = r.id
GROUP  BY t.id, m.study_month, r.id  -- id is PK of respective tables
ORDER  BY t.id, m.study_month, r.id;

Points majeurs

  • Construisez une grille de toutes les combinaisons souhaitées avec CROSS JOIN . Et puis LEFT JOIN aux lignes existantes. Connexe :

  • Dans votre cas, c'est une jointure de plusieurs tables, j'utilise donc des parenthèses dans le FROM liste vers LEFT JOIN au résultat de INNER JOIN entre parenthèses.Ce serait incorrect à LEFT JOIN à chaque table séparément, car vous incluriez les résultats sur les correspondances partielles et obtiendriez des décomptes potentiellement incorrects.

  • En supposant l'intégrité référentielle et en travaillant directement avec les colonnes PK, nous n'avons pas besoin d'inclure rooms et teachers sur le côté gauche une deuxième fois. Mais nous avons toujours une jointure de deux tables (studies et teacher_contacts ). Le rôle de teacher_contacts n'est pas clair pour moi. Normalement, je m'attendrais à une relation entre les studies et teachers directement. Peut-être encore simplifié...

  • Nous devons compter une colonne non nulle sur le côté gauche pour obtenir le nombre souhaité. Comme count(s.room_id)

  • Pour que cela reste rapide pour les grandes tables, assurez-vous que vos prédicats sont sargable . Et ajoutez les index correspondants .

  • La colonne teacher est à peine (fiable) unique. Fonctionne avec un identifiant unique, de préférence le PK (plus rapide et plus simple aussi). J'utilise toujours teacher pour que la sortie corresponde au résultat souhaité. Il peut être judicieux d'inclure un identifiant unique, car les noms peuvent être des doublons.

  • Vous voulez :

    Commencez donc par date_trunc('month', now() - interval '12 month' (pas 13). Cela arrondit déjà le début et fait ce que vous voulez - plus précisément que votre requête d'origine.

Puisque vous avez mentionné des performances lentes, en fonction des définitions de table réelles et de la distribution des données, il est probablement plus rapide de agréger d'abord et de joindre plus tard , comme dans cette réponse connexe :

SELECT upper(trim(t.full_name)) AS teacher
     , m.mon                    AS study_month
     , r.room_code              AS room
     , COALESCE(s.ct, 0)        AS study_count

FROM   teachers t
CROSS  JOIN generate_series(date_trunc('month', now() - interval '12 month')  -- 12!
                          , date_trunc('month', now())
                          , interval '1 month') mon
CROSS  JOIN rooms r
LEFT   JOIN (                                                  -- parentheses!
   SELECT tc.teacher_id, date_trunc('month', s.study_dt) AS mon, s.room_id, count(*) AS ct
   FROM   studies s
   JOIN   teacher_contacts tc ON s.teacher_contact_id = tc.id
   WHERE  s.study_dt >= date_trunc('month', now() - interval '12 month')  -- sargable
   GROUP  BY 1, 2, 3
   ) s ON s.teacher_id = t.id
      AND s.mon = m.mon
      AND s.room_id = r.id
ORDER  BY 1, 2, 3;

À propos de votre remarque de clôture :

Il y a de fortes chances que vous pouviez utiliser la forme à deux paramètres de crosstab() pour produire le résultat souhaité directement et avec d'excellentes performances et la requête ci-dessus n'est pas nécessaire pour commencer. Considérez :