Sulla base di alcuni presupposti (ambiguità nella domanda) suggerisco:
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;
Punti principali
-
Costruisci una griglia di tutte le combinazioni desiderate con
CROSS JOIN
. E poiLEFT JOIN
alle righe esistenti. Correlati: -
Nel tuo caso, è un join di più tabelle, quindi uso le parentesi nel
FROM
elenco suLEFT JOIN
al risultato diINNER JOIN
tra parentesi. Sarebbe errato aLEFT JOIN
a ogni tabella separatamente, perché includerai i risultati nelle partite parziali e otterresti conteggi potenzialmente errati. -
Presupponendo integrità referenziale e lavorando direttamente con le colonne PK, non è necessario includere
rooms
eteachers
sul lato sinistro una seconda volta. Ma abbiamo ancora un join di due tabelle (studies
eteacher_contacts
). Il ruolo diteacher_contacts
non mi è chiaro. Normalmente, mi aspetterei una relazione trastudies
eteachers
direttamente. Potrebbe essere ulteriormente semplificato... -
Dobbiamo contare una colonna non nulla sul lato sinistro per ottenere i conteggi desiderati. Come
count(s.room_id)
-
Per mantenere questo ritmo veloce per i tavoli grandi, assicurati che i tuoi predicati siano sargable . E aggiungi indici corrispondenti .
-
La colonna
teacher
non è (in modo affidabile) unico. Operare con un ID univoco, preferibilmente il PK (anche più veloce e più semplice). Sto ancora usandoteacher
affinché l'output corrisponda al risultato desiderato. Potrebbe essere saggio includere un ID univoco, poiché i nomi possono essere duplicati. -
Vuoi:
Quindi inizia con
date_trunc('month', now() - interval '12 month'
(non 13). Questo sta già arrotondando per difetto l'inizio e fa quello che vuoi, in modo più accurato della tua query originale.
Dato che hai menzionato prestazioni lente, a seconda delle effettive definizioni delle tabelle e della distribuzione dei dati, è probabilmente più veloce aggregare prima e unirti in seguito , come in questa risposta correlata:
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;
Sulla tua osservazione conclusiva:
È probabile che tu puoi usa la forma a due parametri di crosstab()
per produrre il risultato desiderato direttamente e con prestazioni eccellenti e la query di cui sopra non è necessaria per cominciare. Considera: