Използвайки няколко различни функции на прозореца и две подзаявки, това трябва да работи прилично бързо:
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;
Използване на ts
като име за колоната с времеви печат.
Приемаме ts
да бъде уникален – ииндексиран (уникално ограничение прави това автоматично).
В тест с реална таблица с 50 000 реда се нуждаеше само от единично сканиране на индекс . Така че, трябва да бъде прилично бързо дори с големи маси. За сравнение, вашата заявка с join / different не завърши след минута (както се очакваше).
Дори оптимизирана версия, която се занимава с едно кръстосано присъединяване в даден момент (лявото присъединяване с почти ограничено условие е ефективно ограничено кръстосано присъединяване) не завърши след минута.
За най-добра производителност с голяма маса, настройте настройките на паметта си, по-специално за work_mem
(за операции с голям сорт). Помислете да го зададете (много) по-високо за вашата сесия временно, ако можете да пощадите RAM. Прочетете повече тук и тук.
Как?
-
В подзаявка
sub1
погледнете събитието от предишния ред и го запазете само ако е променено, като по този начин маркирате първия елемент от нова група. В същото време вземетеid
на предишния и следващия ред (pre_id
,post_id
). -
В подзаявка
sub2
,count()
отчита само стойности, различни от нула. Получениятgrp
маркира партньори в блокове от последователни същите събития. -
В крайния
SELECT
, вземете първияpre_id
и последниятpost_id
на група за всеки ред, за да стигнете до желания резултат.
Всъщност това трябва да е още по-бързо във външнияSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... тъй като реда на сортиране на прозореца е съгласен с прозореца за
pre_id
, така че е необходим само един сорт. Бърз тест изглежда го потвърждава. Повече за тази дефиниция на рамката.
SQL Fiddle.