Używając kilku różnych funkcji okna i dwóch podzapytań, powinno to działać całkiem szybko:
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;
Korzystanie z ts
jako nazwę kolumny sygnatury czasowej.
Zakładając ts
być unikalnym - i indeksowanym (unikalne ograniczenie robi to automatycznie).
W teście z rzeczywistą tabelą zawierającą 50 tys. wierszy wystarczyło pojedyncze skanowanie indeksu . Więc powinno być przyzwoicie szybkie nawet przy dużych stołach. Dla porównania, twoje zapytanie z join / different nie zakończyło się po minucie (zgodnie z oczekiwaniami).
Nawet zoptymalizowana wersja, obsługująca jedno sprzężenie krzyżowe na raz (lewe sprzężenie prawie nie ograniczające jest skutecznie ograniczone połączenie krzyżowe) nie zakończyło się po minucie.
Aby uzyskać najlepszą wydajność przy dużym stole, dostosuj ustawienia pamięci, w szczególności dla work_mem
(dla dużych operacji sortowania). Rozważ tymczasowe ustawienie jej (znacznie) wyższej dla swojej sesji, jeśli możesz oszczędzić pamięć RAM. Przeczytaj więcej tutaj i tutaj.
Jak?
-
W podzapytaniu
sub1
spójrz na wydarzenie z poprzedniego wiersza i zachowaj je tylko wtedy, gdy się zmieniło, zaznaczając w ten sposób pierwszy element nowej grupy. W tym samym czasie uzyskajid
poprzedniego i następnego wiersza (pre_id
,post_id
). -
W podzapytaniu
sub2
,count()
zlicza tylko wartości inne niż null. Wynikowygrp
zaznacza rówieśników w blokach następujących po sobie tych samych wydarzeń. -
W końcowym
SELECT
, weź pierwszypre_id
i ostatnipost_id
na grupę dla każdego wiersza, aby uzyskać pożądany wynik.
Właściwie powinno to być jeszcze szybsze w zewnętrznymSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... ponieważ porządek sortowania okna zgadza się z oknem dla
pre_id
, więc potrzebny jest tylko jeden rodzaj. Szybki test zdaje się to potwierdzać. Więcej o tej definicji ramki.
Skrzypce SQL.