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

Aggregazione dei record uniti più recenti a settimana

Hai bisogno di un elemento di dati per settimana e obiettivo (prima di aggregare i conteggi per azienda). Questo è un semplice CROSS JOIN tra generate_series() e goals . La parte (forse) costosa è ottenere lo state corrente da updates per ciascuno. Mi piace @Paul già suggerito , un LATERAL join sembra lo strumento migliore. Fallo solo per updates , però, e usa una tecnica più veloce con LIMIT 1 .

E semplifica la gestione della data con date_trunc() .

SELECT w_start
     , g.company_id
     , count(*) FILTER (WHERE u.status = 'green') AS green_count
     , count(*) FILTER (WHERE u.status = 'amber') AS amber_count
     , count(*) FILTER (WHERE u.status = 'red')   AS red_count
FROM   generate_series(date_trunc('week', NOW() - interval '2 months')
                     , date_trunc('week', NOW())
                     , interval '1 week') w_start
CROSS  JOIN goals g
LEFT   JOIN LATERAL (
   SELECT status
   FROM   updates
   WHERE  goal_id = g.id
   AND    created_at < w_start
   ORDER  BY created_at DESC
   LIMIT  1
   ) u ON true
GROUP  BY w_start, g.company_id
ORDER  BY w_start, g.company_id;

Per renderlo veloce hai bisogno di un indice a più colonne :

CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);

Ordine decrescente per created_at è il migliore, ma non strettamente necessario. Postgres può scansionare gli indici all'indietro quasi esattamente alla stessa velocità. ( Tuttavia, non applicabile per l'ordinamento invertito di più colonne. )

Colonne dell'indice in quello ordine. Perché?

E la terza colonna state viene aggiunto solo per consentire scansioni solo indice su updates . Caso correlato:

1.000 obiettivi per 9 settimane (l'intervallo di 2 mesi si sovrappone ad almeno 9 settimane) richiedono solo 9.000 ricerche nell'indice per la seconda tabella di sole 1.000 righe. Per tavoli piccoli come questo, le prestazioni non dovrebbero essere un grosso problema. Ma una volta che ne hai un paio di migliaia in più in ogni tabella, le prestazioni si deterioreranno con le scansioni sequenziali.

w_start rappresenta l'inizio di ogni settimana. Di conseguenza, i conteggi sono per l'inizio della settimana. puoi estrai ancora anno e settimana (o qualsiasi altro dettaglio rappresenta la tua settimana), se insisti:

   EXTRACT(isoyear from w_start) AS year
 , EXTRACT(week    from w_start) AS week

Ideale con ISOYEAR , come ha spiegato @Paul.

SQL Fiddle.

Correlati: