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 JOIN
til eksisterende rækker. Relateret: -
I dit tilfælde er det en sammenføjning af flere tabeller, så jeg bruger parenteser i
FROM
liste tilLEFT JOIN
til resultatet afINNER JOIN
inden for parentes. Det ville være forkert tilLEFT JOIN
til 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
rooms
ogteachers
på venstre side for anden gang. Men vi har stadig en sammenføjning af to tabeller (studies
ogteacher_contacts
). Rollen somteacher_contacts
er uklart for mig. Normalt ville jeg forvente et forhold mellemstudies
ogteachers
direkte. 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
teacher
er næppe (pålideligt) unikt. Arbejd med et unikt ID, helst PK (også hurtigere og enklere). Jeg bruger stadigteacher
for 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: