PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come includere i dati mancanti per più raggruppamenti nell'intervallo di tempo?

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 poi LEFT JOIN alle righe esistenti. Correlati:

  • Nel tuo caso, è un join di più tabelle, quindi uso le parentesi nel FROM elenco su LEFT JOIN al risultato di INNER JOIN tra parentesi. Sarebbe errato a LEFT 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 e teachers sul lato sinistro una seconda volta. Ma abbiamo ancora un join di due tabelle (studies e teacher_contacts ). Il ruolo di teacher_contacts non mi è chiaro. Normalmente, mi aspetterei una relazione tra studies e teachers 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 usando teacher 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: