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 JOIN
do istniejących wierszy. Powiązane: -
W twoim przypadku jest to łączenie kilku tabel, więc używam nawiasów w
FROM
lista doLEFT JOIN
do wyniku zINNER JOIN
w nawiasach. Byłoby nieprawidłowe doLEFT JOIN
do 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ć
rooms
iteachers
po lewej stronie po raz drugi. Ale nadal mamy połączenie dwóch tabel (studies
iteacher_contacts
). Rolateacher_contacts
jest dla mnie niejasne. Normalnie spodziewałbym się związku międzystudies
iteachers
bezpoś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
teacher
jest mało (niezawodnie) wyjątkowy. Działaj z unikalnym identyfikatorem, najlepiej PK (także szybciej i prościej). Nadal używamteacher
aby 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ż: