Ud fra nogle antagelser (uklarheder i spørgsmålet) foreslår jeg:
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;
Vigtige punkter
-
Byg et gitter af alle ønskede kombinationer med
CROSS JOIN. Og derefterLEFT JOINtil eksisterende rækker. Relateret: -
I dit tilfælde er det en sammenføjning af flere tabeller, så jeg bruger parenteser i
FROMliste tilLEFT JOINtil resultatet afINNER JOINinden for parentes. Det ville være forkert tilLEFT JOINtil hver tabel separat, fordi du ville inkludere hits på delvise kampe og få potentielt forkerte optællinger. -
Forudsat henvisningsintegritet og arbejder med PK-kolonner direkte, behøver vi ikke inkludere
roomsogteacherspå venstre side for anden gang. Men vi har stadig en sammenføjning af to tabeller (studiesogteacher_contacts). Rollen somteacher_contactser uklart for mig. Normalt ville jeg forvente et forhold mellemstudiesogteachersdirekte. Måske forenkles yderligere ... -
Vi skal tælle en ikke-nul kolonne i venstre side for at få det ønskede antal. Ligesom
count(s.room_id) -
For at holde dette hurtigt for store borde skal du sørge for, at dine prædikater er sargable . Og tilføj matchende indekser .
-
Kolonnen
teacherer næppe (pålideligt) unikt. Arbejd med et unikt ID, helst PK (også hurtigere og enklere). Jeg bruger stadigteacherfor at outputtet matcher dit ønskede resultat. Det kan være klogt at inkludere et unikt ID, da navne kan være dubletter. -
Du ønsker:
Så start med
date_trunc('month', now() - interval '12 month'(ikke 13). Det runder allerede starten ned og gør, hvad du vil - mere præcist end din oprindelige forespørgsel.
Da du nævnte langsom ydeevne, afhængigt af faktiske tabeldefinitioner og datadistribution, er det sandsynligvis hurtigere at samle først og slutte sig til senere , ligesom i dette relaterede svar:
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 afsluttende bemærkning:
Det er sandsynligt, at du kan brug to-parameter-formen crosstab() at producere dit ønskede resultat direkte og med fremragende ydeevne, og ovenstående forespørgsel er ikke nødvendig til at begynde med. Overvej: