Baserat på några antaganden (otydligheter i frågan) föreslår jag:
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;
Huvudpunkter
-
Bygg ett rutnät av alla önskade kombinationer med
CROSS JOIN. Och sedanLEFT JOINtill befintliga rader. Relaterat: -
I ditt fall är det en sammanfogning av flera tabeller, så jag använder parenteser i
FROMlista tillLEFT JOINtill resultatet avINNER JOINinom parentes. Det skulle vara felaktigt tillLEFT JOINtill varje bord separat, eftersom du skulle inkludera träffar på partiella matchningar och få potentiellt felaktiga räkningar. -
Förutsatt referensintegritet och när vi arbetar med PK-kolumner direkt, behöver vi inte inkludera
roomsochteacherspå vänster sida en andra gång. Men vi har fortfarande en sammanfogning av två tabeller (studiesochteacher_contacts). Rollen förteacher_contactsär oklart för mig. Normalt skulle jag förvänta mig ett förhållande mellanstudiesochteachersdirekt. Kan förenklas ytterligare ... -
Vi måste räkna en kolumn som inte är noll på vänster sida för att få önskat antal. Som
count(s.room_id) -
För att hålla detta snabbt för stora bord, se till att dina predikat är sargable . Och lägg till matchande index .
-
Kolumnen
teachersär knappast (pålitligt) unik. Arbeta med ett unikt ID, helst PK (snabbare och enklare också). Jag använder fortfarandeteachersför att resultatet ska matcha ditt önskade resultat. Det kan vara klokt att inkludera ett unikt ID, eftersom namn kan vara dubbletter. -
Du vill:
Så börja med
date_trunc('month', now() - interval '12 month'(inte 13). Det avrundar redan början och gör vad du vill - mer exakt än din ursprungliga fråga.
Eftersom du nämnde långsam prestanda, beroende på faktiska tabelldefinitioner och datadistribution, är det förmodligen snabbare att aggregera först och gå med senare , som i det här relaterade svaret:
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;
Om din avslutande kommentar:
Chansen är stor att du kan använd tvåparametersformen crosstab() för att producera ditt önskade resultat direkt och med utmärkt prestanda och ovanstående fråga behövs inte till att börja med. Tänk på: