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 JOIN
ein Raster aller gewünschten Kombinationen . Und dannLEFT JOIN
zu bestehenden Reihen. Verwandte: -
In Ihrem Fall handelt es sich um eine Verknüpfung mehrerer Tabellen, daher verwende ich Klammern im
FROM
Liste zuLEFT JOIN
zum Ergebnis vonINNER JOIN
innerhalb der Klammern. Es wäre falsch zuLEFT JOIN
zu 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
rooms
nicht einschließen undteachers
auf der linken Seite ein zweites Mal. Aber wir haben immer noch einen Join von zwei Tabellen (studies
undteacher_contacts
). Die Rolle vonteacher_contacts
ist mir unklar. Normalerweise würde ich eine Beziehung zwischenstudies
erwarten undteachers
direkt. 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
teachers
ist kaum (zuverlässig) einzigartig. Arbeiten Sie mit einer eindeutigen ID, vorzugsweise der PK (auch schneller und einfacher). Ich benutze immer nochteacher
damit 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: