W oparciu o pewne założenia (niejednoznaczności w pytaniu) proponuję:
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;
Główne punkty
-
Zbuduj siatkę wszystkich pożądanych kombinacji za pomocą
CROSS JOIN. A potemLEFT JOINdo istniejących wierszy. Powiązane: -
W twoim przypadku jest to łączenie kilku tabel, więc używam nawiasów w
FROMlista doLEFT JOINdo wyniku zINNER JOINw nawiasach. Byłoby nieprawidłowe doLEFT JOINdo każdej tabeli osobno, ponieważ uwzględniasz trafienia w częściowych dopasowaniach i uzyskujesz potencjalnie nieprawidłowe liczby. -
Zakładając uczciwość referencyjną i pracując bezpośrednio z kolumnami PK, nie musimy uwzględniać
roomsiteacherspo lewej stronie po raz drugi. Ale nadal mamy połączenie dwóch tabel (studiesiteacher_contacts). Rolateacher_contactsjest dla mnie niejasne. Normalnie spodziewałbym się związku międzystudiesiteachersbezpośrednio. Może być jeszcze bardziej uproszczony ... -
Musimy policzyć niezerową kolumnę po lewej stronie, aby uzyskać żądane liczby. Podobnie jak
count(s.room_id) -
Aby zachować szybkość w przypadku dużych tabel, upewnij się, że predykaty są możliwe do sargowania . I dodaj pasujące indeksy .
-
Kolumna
teacherjest mało (niezawodnie) wyjątkowy. Działaj z unikalnym identyfikatorem, najlepiej PK (także szybciej i prościej). Nadal używamteacheraby wynik pasował do pożądanego rezultatu. Rozsądne może być dołączenie unikalnego identyfikatora, ponieważ nazwy mogą być duplikatami. -
Chcesz:
Zacznij więc od
date_trunc('month', now() - interval '12 month'(nie 13). To już zaokrągla początek i robi to, co chcesz - dokładniej niż oryginalne zapytanie.
Ponieważ wspomniałeś o niskiej wydajności, zależnej od rzeczywistych definicji tabel i dystrybucji danych, prawdopodobnie szybciej będzie agregować najpierw, a później dołączyć , jak w tej powiązanej odpowiedzi:
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 Twojej uwadze końcowej:
Są szanse, możesz użyj dwuparametrowej formy crosstab() aby uzyskać pożądany wynik bezpośrednio i z doskonałą wydajnością, a powyższe zapytanie nie jest potrzebne na początku. Rozważ: