Berdasarkan beberapa asumsi (ambiguitas dalam pertanyaan) saya menyarankan:
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;
Poin utama
-
Buat kisi dari semua kombinasi yang diinginkan dengan
CROSS JOIN
. Dan kemudianLEFT JOIN
ke baris yang ada. Terkait: -
Dalam kasus Anda, ini adalah gabungan dari beberapa tabel, jadi saya menggunakan tanda kurung di
FROM
daftar keLEFT JOIN
ke hasil dariINNER JOIN
dalam tanda kurung. Ini akan menjadi salah keLEFT JOIN
ke setiap tabel secara terpisah, karena Anda akan menyertakan klik pada kecocokan sebagian dan mendapatkan penghitungan yang berpotensi salah. -
Dengan asumsi integritas referensial dan bekerja dengan kolom PK secara langsung, kita tidak perlu menyertakan
rooms
danteachers
di sisi kiri untuk kedua kalinya. Tapi kami masih memiliki gabungan dua tabel (studies
danteacher_contacts
). Peranteacher_contacts
tidak jelas bagi saya. Biasanya, saya mengharapkan hubungan antarastudies
danteachers
secara langsung. Mungkin lebih disederhanakan ... -
Kita perlu menghitung kolom non-null di sisi kiri untuk mendapatkan jumlah yang diinginkan. Sukai
count(s.room_id)
-
Agar ini tetap cepat untuk tabel besar, pastikan predikat Anda sargable . Dan tambahkan indeks yang cocok .
-
Kolom
teachers
hampir (dapat diandalkan) unik. Beroperasi dengan ID unik, lebih disukai PK (lebih cepat dan lebih sederhana juga). Saya masih menggunakanteachers
untuk output agar sesuai dengan hasil yang Anda inginkan. Sebaiknya sertakan ID unik, karena nama dapat digandakan. -
Anda ingin:
Jadi mulailah dengan
date_trunc('month', now() - interval '12 month'
(bukan 13). Itu sudah membulatkan awal dan melakukan apa yang Anda inginkan - lebih akurat daripada kueri awal Anda.
Karena Anda menyebutkan kinerja yang lambat, bergantung pada definisi tabel yang sebenarnya dan distribusi data, mungkin lebih cepat menggabungkan terlebih dahulu dan bergabung kemudian , seperti dalam jawaban terkait ini:
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;
Tentang kata penutup Anda:
Kemungkinan Anda bisa gunakan bentuk dua parameter crosstab()
untuk menghasilkan hasil yang Anda inginkan secara langsung dan dengan kinerja yang sangat baik dan permintaan di atas tidak diperlukan untuk memulai. Pertimbangkan: