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 JOIN
till befintliga rader. Relaterat: -
I ditt fall är det en sammanfogning av flera tabeller, så jag använder parenteser i
FROM
lista tillLEFT JOIN
till resultatet avINNER JOIN
inom parentes. Det skulle vara felaktigt tillLEFT JOIN
till 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
rooms
ochteachers
på vänster sida en andra gång. Men vi har fortfarande en sammanfogning av två tabeller (studies
ochteacher_contacts
). Rollen förteacher_contacts
är oklart för mig. Normalt skulle jag förvänta mig ett förhållande mellanstudies
ochteachers
direkt. 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 fortfarandeteachers
fö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å: