Sie benötigen ein Datenelement pro Woche und Ziel (vor der Aggregation der Zählungen pro Unternehmen). Das ist ein einfacher CROSS JOIN
zwischen generate_series()
und goals
. Der (möglicherweise) teure Teil besteht darin, den aktuellen state
zu erhalten von updates
für jeden. Wie @Paul bereits vorgeschlagen
, ein LATERAL
join scheint das beste Werkzeug zu sein. Tun Sie dies nur für updates
, und verwenden Sie eine schnellere Technik mit LIMIT 1
.
Und vereinfachen Sie die Datumsverwaltung mit 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;
Um dies schnell zu machen Sie benötigen einen mehrspaltigen Index :
CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);
Absteigende Reihenfolge für created_at
ist am besten, aber nicht unbedingt erforderlich. Postgres kann Indizes fast genauso schnell rückwärts scannen. ( Gilt jedoch nicht für umgekehrte Sortierreihenfolge mehrerer Spalten.
)
Indexspalten in dass bestellen. Warum?
Und die dritte Spalte status
wird nur angehängt, um schnelle Nur-Index-Scans
zu ermöglichen auf updates
. Verwandter Fall:
1.000 Ziele für 9 Wochen (Ihr Intervall von 2 Monaten überschneidet sich mit mindestens 9 Wochen) erfordern nur 9.000 Indexsuchen für die 2. Tabelle mit nur 1.000 Zeilen. Bei kleinen Tabellen wie dieser sollte die Leistung kein großes Problem darstellen. Aber sobald Sie ein paar Tausend mehr in jeder Tabelle haben, verschlechtert sich die Leistung bei sequentiellen Scans.
w_start
stellt den Beginn jeder Woche dar. Die Zählungen beziehen sich daher auf den Wochenstart. Sie können Extrahieren Sie trotzdem Jahr und Woche (oder andere Details, die Ihre Woche darstellen), wenn Sie darauf bestehen:
EXTRACT(isoyear from w_start) AS year
, EXTRACT(week from w_start) AS week
Am besten mit ISOYEAR
, wie @Paul erklärt hat.
Verwandte:
- Was ist der Unterschied zwischen LATERAL und einer Unterabfrage in PostgreSQL?
- GRUPPE NACH-Abfrage optimieren, um den neuesten Datensatz pro Benutzer abzurufen
- Zuerst auswählen Zeile in jeder GROUP BY-Gruppe?
- PostgreSQL:Laufende Zeilenzählung für eine Abfrage 'minütlich'