Genom att använda flera olika fönsterfunktioner och två underfrågor borde detta fungera hyfsat snabbt:
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;
Använder ts
som namn för tidsstämpelkolumnen.
Anta ts
att vara unik – och indexerad (en unik begränsning gör det automatiskt).
I ett test med en tabell i verkligheten med 50 000 rader behövdes bara en enda indexskanning . Så, bör vara hyfsat snabb även med stora bord. Som jämförelse slutfördes inte din fråga med join / distinct efter en minut (som förväntat).
Även en optimerad version, som hanterar en korsfogning i taget (vänsterfogningen med knappast ett begränsande villkor är i praktiken en begränsad cross join) avslutades inte efter en minut.
För bästa prestanda med ett stort bord, justera dina minnesinställningar, särskilt för work_mem
(för stora sorters operationer). Överväg att ställa in den (mycket) högre för din session tillfälligt om du kan spara RAM-minnet. Läs mer här och här.
Hur?
-
I underfrågan
sub1
titta på händelsen från föregående rad och behåll den bara om den har ändrats, vilket markerar det första elementet i en ny grupp. Skaffa samtidigtid
av föregående och nästa rad (pre_id
,post_id
). -
I underfrågan
sub2
,count()
räknar endast icke-nullvärden. Den resulterandegrp
markerar kamrater i block av på varandra följande samma händelser. -
I den sista
SELECT
, ta det förstapre_id
och det sistapost_id
per grupp för varje rad för att komma fram till önskat resultat.
Egentligen borde detta vara ännu snabbare i den yttreSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... eftersom sorteringsordningen för fönstret överensstämmer med fönstret för
pre_id
, så bara en enda sortering behövs. Ett snabbt test verkar bekräfta det. Mer om denna ramdefinition.
SQL-fiol.