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.
Correlati:
- Qual è la differenza tra LATERAL e una sottoquery in PostgreSQL?
- Ottimizza la query GROUP BY per recuperare l'ultimo record per utente
- Seleziona prima riga in ogni gruppo GROUP BY?
- PostgreSQL:conteggio in esecuzione delle righe per una query 'per minuto'