PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie können fehlende Daten für mehrere Gruppierungen innerhalb der Zeitspanne aufgenommen werden?

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 dann LEFT JOIN zu bestehenden Reihen. Verwandte:

  • In Ihrem Fall handelt es sich um eine Verknüpfung mehrerer Tabellen, daher verwende ich Klammern im FROM Liste zu LEFT JOIN zum Ergebnis von INNER JOIN innerhalb der Klammern. Es wäre falsch zu LEFT 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 und teachers auf der linken Seite ein zweites Mal. Aber wir haben immer noch einen Join von zwei Tabellen (studies und teacher_contacts ). Die Rolle von teacher_contacts ist mir unklar. Normalerweise würde ich eine Beziehung zwischen studies erwarten und teachers 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 noch teacher 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: