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 puisLEFT 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 versLEFT JOIN
au résultat deINNER 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
etteachers
sur le côté gauche une deuxième fois. Mais nous avons toujours une jointure de deux tables (studies
etteacher_contacts
). Le rôle deteacher_contacts
n'est pas clair pour moi. Normalement, je m'attendrais à une relation entre lesstudies
etteachers
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 toujoursteacher
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 :