Potrzebujesz jednego elementu danych na tydzień i celu (przed agregacją liczby na firmę). To jest zwykłe CROSS JOIN
między generate_series()
i goals
. (Prawdopodobnie) kosztowną częścią jest uzyskanie aktualnego state
z updates
dla każdego. Jak @Paul już zasugerował
, LATERAL
join wydaje się najlepszym narzędziem. Zrób to tylko dla updates
, i użyj szybszej techniki z LIMIT 1
.
I uprość obsługę dat dzięki 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;
Aby zrobić to szybko potrzebujesz indeksu wielokolumnowego :
CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);
Malejąca kolejność dla created_at
jest najlepszy, ale nie bezwzględnie konieczny. Postgres może skanować indeksy do tyłu niemal dokładnie tak samo szybko. ( Nie dotyczy jednak odwróconej kolejności sortowania wielu kolumn.
)
Indeksuj kolumny w tym zamówienie. Dlaczego?
I trzecia kolumna status
jest dołączany tylko w celu umożliwienia szybkiego skanowania tylko do indeksu
o updates
. Powiązany przypadek:
1000 goli na 9 tygodni (twój interwał 2 miesięcy pokrywa się z co najmniej 9 tygodniami) wymaga jedynie sprawdzenia indeksu 9 tys. dla drugiej tabeli zawierającej tylko 1 tys. wierszy. W przypadku małych tabel, takich jak ta, wydajność nie powinna stanowić większego problemu. Ale gdy będziesz mieć kilka tysięcy więcej w każdej tabeli, wydajność pogorszy się przy skanowaniu sekwencyjnym.
w_start
reprezentuje początek każdego tygodnia. W związku z tym liczenia są na początek tygodnia. możesz nadal wyodrębnij rok i tydzień (lub inne szczegóły reprezentują Twój tydzień), jeśli nalegasz:
EXTRACT(isoyear from w_start) AS year
, EXTRACT(week from w_start) AS week
Najlepiej z ISOYEAR
, jak wyjaśnił @Paul.
Powiązane:
- Jaka jest różnica między LATERAL a podzapytanie w PostgreSQL?
- Zoptymalizuj zapytanie GROUP BY, aby pobrać najnowszy rekord na użytkownika
- Wybierz najpierw wiersz w każdej grupie GROUP BY?
- PostgreSQL:liczenie wierszy dla zapytania 'w minutach'