Ved at bruge flere forskellige vinduesfunktioner og to underforespørgsler burde dette fungere anstændigt hurtigt:
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;
Brug af ts
som navn for tidsstempelkolonnen.
Under forudsætning af ts
at være unik - og indekseret (en unik begrænsning gør det automatisk).
I en test med en virkelig tabel med 50.000 rækker behøvede den kun en enkelt indeksscanning . Så bør være anstændigt hurtig selv med store borde. Til sammenligning blev din forespørgsel med join / distinct ikke afsluttet efter et minut (som forventet).
Selv en optimeret version, der handler om én krydssammenføjning ad gangen (den venstre sammenføjning med næppe en begrænsende betingelse er faktisk en begrænset cross join) blev ikke færdig efter et minut.
For at få den bedste ydeevne med et stort bord skal du justere dine hukommelsesindstillinger, især for work_mem
(til store sorteringsoperationer). Overvej at sætte den (meget) højere for din session midlertidigt, hvis du kan spare RAM. Læs mere her og her.
Hvordan?
-
I underforespørgsel
sub1
se på begivenheden fra den forrige række og behold kun den, hvis den har ændret sig, og markerer dermed det første element i en ny gruppe. Få samtidigid
af den forrige og den næste række (pre_id
,post_id
). -
I underforespørgsel
sub2
,count()
tæller kun ikke-nul værdier. Den resulterendegrp
markerer jævnaldrende i blokke af på hinanden følgende samme begivenheder. -
I den sidste
SELECT
, tag det førstepre_id
og den sidstepost_id
gruppe for hver række for at nå frem til det ønskede resultat.
Det burde faktisk være endnu hurtigere i den ydreSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... da sorteringsrækkefølgen af vinduet stemmer overens med vinduet for
pre_id
, så kun en enkelt sortering er nødvendig. En hurtig test ser ud til at bekræfte det. Mere om denne rammedefinition.
SQL Fiddle.