Du behöver en datapost per vecka och mål (innan du sammanställer antal per företag). Det är en vanlig CROSS JOIN
mellan generate_series()
och goals
. Den (möjligen) dyra delen är att få det nuvarande state
från updates
för varje. Som @Paul redan föreslagit
, en LATERAL
gå med verkar vara det bästa verktyget. Gör det bara för updates
, dock, och använd en snabbare teknik med LIMIT 1
.
Och förenkla datumhanteringen med 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;
För att göra detta snabbt du behöver ett flerkolumnindex :
CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);
Fallande ordning för created_at
är bäst, men inte absolut nödvändigt. Postgres kan skanna index bakåt nästan exakt lika snabbt. ( Inte tillämpligt för inverterad sorteringsordning för flera kolumner, dock.
)
Indexera kolumner i det ordning. Varför?
Och den tredje kolumnen status
är endast tillagd för att tillåta snabba endast indexsökningar
på updates
. Relaterat fall:
1 000 mål under 9 veckor (ditt intervall på 2 månader överlappar med minst 9 veckor) kräver bara 9 000 indexuppslag för den andra tabellen med endast 1 000 rader. För små bord som detta borde prestanda inte vara några större problem. Men när du väl har ett par tusen fler i varje tabell kommer prestandan att försämras med sekventiella skanningar.
w_start
representerar början av varje vecka. Följaktligen gäller räkningar för början av veckan. Du kan extrahera fortfarande år och vecka (eller andra detaljer representerar din vecka), om du insisterar:
EXTRACT(isoyear from w_start) AS year
, EXTRACT(week from w_start) AS week
Bäst med ISOYEAR
, som @Paul förklarade.
Relaterat:
- Vad är skillnaden mellan LATERAL och en underfråga i PostgreSQL?
- Optimera GROUP BY-frågan för att hämta senaste posten per användare
- Välj först rad i varje GROUP BY-grupp?
- PostgreSQL:löpande antal rader för en fråga "per minut"