Mit mehreren verschiedenen Fensterfunktionen und zwei Unterabfragen sollte dies recht schnell funktionieren:
WITH events(id, event, ts) AS (
VALUES
(1, 12, '2014-03-19 08:00:00'::timestamp)
,(2, 12, '2014-03-19 08:30:00')
,(3, 13, '2014-03-19 09:00:00')
,(4, 13, '2014-03-19 09:30:00')
,(5, 12, '2014-03-19 10:00:00')
)
SELECT first_value(pre_id) OVER (PARTITION BY grp ORDER BY ts) AS pre_id
, id, ts
, first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM (
SELECT *, count(step) OVER w AS grp
FROM (
SELECT id, ts
, NULLIF(lag(event) OVER w, event) AS step
, lag(id) OVER w AS pre_id
, lead(id) OVER w AS post_id
FROM events
WINDOW w AS (ORDER BY ts)
) sub1
WINDOW w AS (ORDER BY ts)
) sub2
ORDER BY ts;
Verwenden von ts
als Name für die Zeitstempelspalte.
Angenommen ts
eindeutig sein - und indiziert (Eine eindeutige Einschränkung macht das automatisch).
In einem Test mit einer realen Tabelle mit 50.000 Zeilen war nur ein einziger Index-Scan erforderlich . Sollte also auch bei großen Tischen anständig schnell sein. Im Vergleich dazu wurde Ihre Abfrage mit join / distinct nicht nach einer Minute beendet (wie erwartet).
Sogar eine optimierte Version, die jeweils nur einen Cross-Join behandelt (der linke Join mit kaum einer einschränkenden Bedingung ist effektiv eine begrenzte cross join) wurde nach einer Minute nicht beendet.
Optimieren Sie für eine optimale Leistung mit einer großen Tabelle Ihre Speichereinstellungen, insbesondere für work_mem
(für große Sortieroperationen). Erwägen Sie, es für Ihre Sitzung vorübergehend (viel) höher einzustellen, wenn Sie den Arbeitsspeicher entbehren können. Lesen Sie mehr hier und hier.
Wie?
-
In Unterabfrage
sub1
Sehen Sie sich das Ereignis aus der vorherigen Zeile an und behalten Sie es nur, wenn es sich geändert hat, und markieren Sie so das erste Element einer neuen Gruppe. Holen Sie sich gleichzeitig dieid
der vorherigen und der nächsten Zeile (pre_id
,post_id
). -
In Unterabfrage
sub2
,count()
zählt nur Nicht-Null-Werte. Die resultierendegrp
markiert Peers in Blöcken aufeinanderfolgender gleicher Ereignisse. -
Im letzten
SELECT
, nehmen Sie die erstepre_id
und die letztepost_id
pro Gruppe für jede Zeile, um zum gewünschten Ergebnis zu kommen.
Eigentlich sollte das im äußerenSELECT
noch schneller gehen :last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... da die Sortierreihenfolge des Fensters mit dem Fenster für
pre_id
übereinstimmt , sodass nur eine einzige Sortierung erforderlich ist. Ein Schnelltest scheint es zu bestätigen. Mehr über diese Rahmendefinition.
SQL-Geige.