Op basis van enkele aannames (ambiguïteiten in de vraag) stel ik voor:
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;
Belangrijkste punten
-
Bouw een raster van alle gewenste combinaties met
CROSS JOIN. En danLEFT JOINnaar bestaande rijen. Gerelateerd: -
In jouw geval is het een samenvoeging van meerdere tabellen, dus ik gebruik haakjes in de
FROMlijst naarLEFT JOINnaar het resultaat vanINNER JOINtussen haakjes. Het zou onjuist zijn naarLEFT JOINnaar elke tafel afzonderlijk, omdat je dan treffers op gedeeltelijke overeenkomsten zou opnemen en mogelijk onjuiste tellingen zou krijgen. -
Uitgaande van referentiële integriteit en als we rechtstreeks met PK-kolommen werken, hoeven we geen
roomsop te nemen enteacherseen tweede keer aan de linkerkant. Maar we hebben nog steeds een combinatie van twee tabellen (studiesenteacher_contacts). De rol vanteacher_contactsis mij onduidelijk. Normaal gesproken zou ik een verband verwachten tussenstudiesenteachersdirect. Kan verder worden vereenvoudigd ... -
We moeten een niet-null-kolom aan de linkerkant tellen om de gewenste tellingen te krijgen. Like
count(s.room_id) -
Om dit snel te houden voor grote tafels, moet u ervoor zorgen dat uw predikaten sargable zijn . En voeg bijpassende indexen toe .
-
De kolom
teacheris nauwelijks (betrouwbaar) uniek. Werk met een unieke ID, bij voorkeur de PK (sneller en eenvoudiger ook). Ik gebruik nog steedsteacherzodat de uitvoer overeenkomt met uw gewenste resultaat. Het kan verstandig zijn om een unieke ID op te nemen, aangezien namen dubbel kunnen zijn. -
Je wilt:
Dus begin met
date_trunc('month', now() - interval '12 month'(niet 13). Dat is al een afronding naar beneden en doet wat u wilt - nauwkeuriger dan uw oorspronkelijke zoekopdracht.
Aangezien u trage prestaties noemde, afhankelijk van de werkelijke tabeldefinities en gegevensdistributie, is het waarschijnlijk sneller om eerst samen te voegen en later deel te nemen , zoals in dit gerelateerde antwoord:
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;
Over je slotopmerking:
De kans is groot dat je kunt gebruik de twee-parametervorm van crosstab() om uw gewenste resultaat direct en met uitstekende prestaties te produceren en de bovenstaande vraag is om te beginnen niet nodig. Overweeg: