Basierend auf einigen Annahmen (Mehrdeutigkeiten in der Frage) schlage ich vor:
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;
Wichtige Punkte
-
Erstellen Sie mit
CROSS JOINein Raster aller gewünschten Kombinationen . Und dannLEFT JOINzu bestehenden Reihen. Verwandte: -
In Ihrem Fall handelt es sich um eine Verknüpfung mehrerer Tabellen, daher verwende ich Klammern im
FROMListe zuLEFT JOINzum Ergebnis vonINNER JOINinnerhalb der Klammern. Es wäre falsch zuLEFT JOINzu jeder Tabelle separat, da Sie Treffer auf teilweise Übereinstimmungen einbeziehen und potenziell falsche Zählungen erhalten würden. -
Annahme von referenzieller Integrität und wenn wir direkt mit PK-Spalten arbeiten, müssen wir
roomsnicht einschließen undteachersauf der linken Seite ein zweites Mal. Aber wir haben immer noch einen Join von zwei Tabellen (studiesundteacher_contacts). Die Rolle vonteacher_contactsist mir unklar. Normalerweise würde ich eine Beziehung zwischenstudieserwarten undteachersdirekt. Könnte weiter vereinfacht werden ... -
Wir müssen eine Nicht-Null-Spalte auf der linken Seite zählen, um die gewünschten Zählungen zu erhalten. Wie
count(s.room_id) -
Um dies für große Tabellen schnell zu halten, stellen Sie sicher, dass Ihre Prädikate sargable sind . Und fügen Sie passende Indizes hinzu .
-
Die Spalte
teachersist kaum (zuverlässig) einzigartig. Arbeiten Sie mit einer eindeutigen ID, vorzugsweise der PK (auch schneller und einfacher). Ich benutze immer nochteacherdamit die Ausgabe Ihrem gewünschten Ergebnis entspricht. Es kann ratsam sein, eine eindeutige ID anzugeben, da Namen doppelt vorkommen können. -
Sie wollen:
Beginnen Sie also mit
date_trunc('month', now() - interval '12 month'(nicht 13). Das rundet den Anfang bereits ab und macht das, was Sie wollen - genauer als Ihre ursprüngliche Abfrage.
Da Sie die langsame Leistung erwähnt haben, ist es je nach tatsächlichen Tabellendefinitionen und Datenverteilung wahrscheinlich schneller, zuerst zu aggregieren und später beizutreten , wie in dieser verwandten Antwort:
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;
Zu Ihrer Schlussbemerkung:
Die Chancen stehen gut, dass Sie können Verwenden Sie die Zwei-Parameter-Form von crosstab() um Ihr gewünschtes Ergebnis direkt und mit hervorragender Performance zu erzeugen und die obige Abfrage wird zunächst nicht benötigt. Bedenken Sie: