Na základě některých předpokladů (nejednoznačností v otázce) navrhuji:
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;
Hlavní body
-
Vytvořte mřížku všech požadovaných kombinací pomocí
CROSS JOIN
. A pakLEFT JOIN
do stávajících řádků. Související: -
Ve vašem případě se jedná o spojení několika tabulek, proto používám závorky v
FROM
seznamLEFT JOIN
k výsledku zINNER JOIN
v závorkách.Bylo by to nesprávné doLEFT JOIN
ke každé tabulce zvlášť, protože byste zahrnuli výsledky dílčích zápasů a získali potenciálně nesprávné počty. -
Za předpokladu referenční integrity a pracujeme přímo se sloupci PK, nemusíme zahrnovat
rooms
ateachers
na levé straně podruhé. Ale stále máme spojení dvou tabulek (studies
ateacher_contacts
). Roleteacher_contacts
je mi nejasný. Normálně bych očekával vztah mezistudies
ateachers
přímo. Mohlo by být dále zjednodušeno ... -
Potřebujeme spočítat nenulový sloupec na levé straně, abychom získali požadované počty. Jako
count(s.room_id)
-
Abyste to u velkých stolů udrželi rychle, ujistěte se, že vaše predikáty jsou sargable . A přidejte odpovídající indexy .
-
Sloupec
teacher
je stěží (spolehlivě) unikátní. Pracujte s jedinečným ID, nejlépe PK (rovněž rychlejší a jednodušší). Stále používámteacher
aby výstup odpovídal požadovanému výsledku. Může být rozumné zahrnout jedinečné ID, protože jména mohou být duplicitní. -
Chcete:
Začněte tedy s
date_trunc('month', now() - interval '12 month'
(ne 13). To už začátek zaokrouhluje dolů a dělá to, co chcete – přesněji než váš původní dotaz.
Vzhledem k tomu, že jste zmínil pomalý výkon v závislosti na skutečných definicích tabulek a distribuci dat, je pravděpodobně rychlejší nejprve agregovat a později se připojit , jako v této související odpovědi:
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;
O vaší závěrečné poznámce:
Je pravděpodobné, že můžete použijte dvouparametrovou formu crosstab()
k dosažení požadovaného výsledku přímo a s vynikajícím výkonem a výše uvedený dotaz není pro začátek potřeba. Zvažte: