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 JOIN
naar bestaande rijen. Gerelateerd: -
In jouw geval is het een samenvoeging van meerdere tabellen, dus ik gebruik haakjes in de
FROM
lijst naarLEFT JOIN
naar het resultaat vanINNER JOIN
tussen haakjes. Het zou onjuist zijn naarLEFT JOIN
naar 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
rooms
op te nemen enteachers
een tweede keer aan de linkerkant. Maar we hebben nog steeds een combinatie van twee tabellen (studies
enteacher_contacts
). De rol vanteacher_contacts
is mij onduidelijk. Normaal gesproken zou ik een verband verwachten tussenstudies
enteachers
direct. 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
teacher
is nauwelijks (betrouwbaar) uniek. Werk met een unieke ID, bij voorkeur de PK (sneller en eenvoudiger ook). Ik gebruik nog steedsteacher
zodat 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: