Въз основа на някои предположения (неясноти във въпроса) предлагам:
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;
Основни точки
-
Изградете мрежа от всички желани комбинации с
CROSS JOIN
. И след товаLEFT JOIN
към съществуващи редове. Свързани: -
Във вашия случай това е обединение на няколко таблици, така че използвам скоби в
FROM
списък къмLEFT JOIN
към резултата наINNER JOIN
в скобите. Би било неправилно къмLEFT JOIN
към всяка таблица поотделно, тъй като ще включите попадения на частични съвпадения и ще получите потенциално неправилен брой. -
Приемане на референтна цялост и работейки директно с PK колони, не е необходимо да включваме
rooms
иteachers
от лявата страна втори път. Но все още имаме съединяване на две таблици (studies
иteacher_contacts
). Ролята наteacher_contacts
не ми е ясно. Обикновено бих очаквал връзка междуstudies
иteachers
директно. Може да бъде допълнително опростено... -
Трябва да преброим ненулева колона от лявата страна, за да получим желаните преброявания. Като
count(s.room_id)
-
За да поддържате това бързо за големи таблици, уверете се, че вашите предикати са sargable . И добавете съответстващи индекси .
-
Колоната
teacher
едва ли е (надеждно) уникален. Работете с уникален идентификатор, за предпочитане PK (също по-бързо и по-лесно). Все още използвамteacher
така че изходът да съответства на желания резултат. Може да е разумно да включите уникален идентификатор, тъй като имената могат да се дублират. -
Искате:
Затова започнете с
date_trunc('month', now() - interval '12 month'
(не 13). Това вече закръглява началото и прави това, което искате - по-точно от първоначалната ви заявка.
Тъй като споменахте бавна производителност, в зависимост от действителните дефиниции на таблици и разпространението на данни, вероятно е по-бързо да първо обобщите и да се присъедините по-късно , като в този свързан отговор:
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;
За заключителната ви бележка:
Вероятно можете използвайте формата с два параметъра на crosstab()
за да произведете желания резултат директно и с отлична производителност и горната заявка не е необходима като начало. Помислете за следното: