Запрос на «прогон» последовательных столбцов в Postgres - PullRequest
1 голос
/ 23 ноября 2011

У меня есть таблица:

create table table1 (event_id integer, event_time timestamp without time zone);
insert into table1 (event_id, event_time) values
(1, '2011-01-01 00:00:00'),
(2, '2011-01-01 00:00:15'),
(3, '2011-01-01 00:00:29'),
(4, '2011-01-01 00:00:58'),
(5, '2011-01-02 06:03:00'),
(6, '2011-01-02 06:03:09'),
(7, '2011-01-05 11:01:31'),
(8, '2011-01-05 11:02:15'),
(9, '2011-01-06 09:34:19'),
(10, '2011-01-06 09:34:41'),
(11, '2011-01-06 09:35:06');

Я хотел бы построить оператор, который при заданном событии мог бы возвращать длину «прогона» событий, начинающихся с этого события.Прогон определяется следующим образом:

  1. Два события выполняются вместе, если они находятся в пределах 30 секунд друг от друга.
  2. Если A и B выполняются вместе, а B и C выполняются вместе, то A выполняется с C.

Однако мой запрос не требуетвернуться назад во времени, поэтому, если я выберу событие 2, то только события 2, 3 и 4 должны учитываться как часть серии событий, начинающихся с 2, а 3 должна возвращаться как длина цикла.

Есть идеи?Я в тупике.

Ответы [ 3 ]

1 голос
/ 23 ноября 2011

Вы можете присоединить таблицу к себе в отчете о разнице дат.На самом деле это postgres, работает простой минус.

Этот подзапрос найдет все записи, которые являются «начальными событиями».То есть все записи о событиях, у которых нет другой записи о событии, произошедшей в течение 30 секунд до этого:

(Select a.event_id, a.event_time from
(Select event_id, event_time from table1) a
 left join 
 (select event_id, event_time from table1) b
 on a.event_time - b.event_time < '00:00:30' and a.event_time - b.event_time > '00:00:00'
 where b.event_time is null) startevent

С несколькими изменениями ... та же логика, за исключением выбора события 'end':

(Select a.event_id, a.event_time from
(Select event_id, event_time from table1) a
 left join 
 (select event_id, event_time from table1) b
 on b.event_time - a.event_time < '00:00:30' and b.event_time - a.event_time > '00:00:00'
 where b.event_time is null) end_event

Теперь мы можем объединить их вместе, чтобы связать, какое начальное событие идет к какому конечному событию:

(все еще пишу ... есть несколько способов сделать это. Япри условии, что только в примере имеются линейные идентификационные номера, поэтому вы захотите объединить время начального события с временем конечного события, имеющим наименьшую положительную разницу во времени события).

Вот мой конечный результат ... своего родавложенные много вложенных элементов

 select a.start_id, case when a.event_id is null then t1.event_id::varchar else 'single  event' end as end_id
 from
 (select start_event.event_id as start_id, start_event.event_time as start_time,      last_event.event_id, min(end_event.event_time - start_event.event_time) as min_interval   
 from
    (Select a.event_id, a.event_time from
    (Select event_id, event_time from table1) a
     left join 
    (select event_id, event_time from table1) b
   on a.event_time - b.event_time < '00:00:30' and a.event_time - b.event_time > '00:00:00'
 where b.event_time is null) start_event

inner join

   (Select a.event_id, a.event_time from
(Select event_id, event_time from table1) a
 left join 
 (select event_id, event_time from table1) b
 on b.event_time - a.event_time < '00:00:30' and b.event_time - a.event_time > '00:00:00'
 where b.event_time is null) end_event     
on end_event.event_time > start_event.event_time

--check for only event
 left join
 (Select a.event_id, a.event_time from
 (Select event_id, event_time from table1) a
  left join 
  (select event_id, event_time from table1) b
  on b.event_time - a.event_time < '00:00:30' and b.event_time - a.event_time > '00:00:00'
  where b.event_time is null) last_event
    on start_event.event_id = last_event.event_id
group by 1,2,3) a
    left join table1 t1 on t1.event_time = a.start_time + a.min_interval

Результаты в виде start_id, end_Id:

1; "4"
5; "6"
7; "одиночное событие"
8; "одиночное событие"
9; "11"

Мне пришлось использовать третье левое соединение, чтобы выбрать отдельные события как метод обнаружения событий, которые были как начальными, так и конечными событиями.Конечный результат находится в идентификаторах и может быть связан с вашей исходной таблицей, если вам нужна другая информация, чем просто идентификатор.Не знаете, как будет масштабироваться это решение, если у вас миллионы событий ... это может быть проблемой.

1 голос
/ 24 ноября 2011

Вот рекурсивное CTE-решение. (проблемы островков и пробелов, естественно, поддаются рекурсивному CTE)

WITH RECURSIVE runrun AS (
    SELECT event_id, event_time
    , event_time - ('30 sec'::interval) AS low_time
    , event_time + ('30 sec'::interval) AS high_time
    FROM table1
    UNION
    SELECT t1.event_id, t1.event_time
    , LEAST ( rr.low_time, t1.event_time - ('30 sec'::interval) ) AS low_time
    , GREATEST ( rr.high_time, t1.event_time + ('30 sec'::interval) ) AS high_time
    FROM table1 t1
    JOIN runrun rr ON t1.event_time >= rr.low_time
                  AND t1.event_time < rr.high_time
    )
SELECT DISTINCT ON (event_id) *
FROM runrun rr
WHERE rr.event_time >= '2011-01-01 00:00:15'
AND rr.low_time <= '2011-01-01 00:00:15'
AND rr.high_time > '2011-01-01 00:00:15'
    ;

Результат:

 event_id |     event_time      |      low_time       |      high_time      
----------+---------------------+---------------------+---------------------
        2 | 2011-01-01 00:00:15 | 2010-12-31 23:59:45 | 2011-01-01 00:00:45
        3 | 2011-01-01 00:00:29 | 2010-12-31 23:59:45 | 2011-01-01 00:01:28
        4 | 2011-01-01 00:00:58 | 2010-12-31 23:59:30 | 2011-01-01 00:01:28
(3 rows)
1 голос
/ 23 ноября 2011

Может выглядеть так:

WITH x AS (
    SELECT event_time
          ,row_number() OVER w AS rn
          ,lead(event_time) OVER w AS next_time
    FROM   table1
    WHERE  event_id >= <start_id>
    WINDOW w AS (ORDER BY event_time, event_id)
    )
SELECT COALESCE(
      (SELECT x.rn
       FROM   x
       WHERE  (x.event_time + interval '30s') < x.next_time
       ORDER  BY x.rn
       LIMIT  1)
     ,(SELECT count(*) FROM x)
      ) AS run_length

Эта версия не использует последовательность идентификаторов без пробелов, а только event_time.
Идентичные event_time дополнительно сортируются по event_id для однозначности.

Прочтите о оконных функциях row_number() и lead() и CTE (с предложением) в руководстве.

Редактировать

Если мы не можем предположить, что больший event_id имеет более поздний (или равный) event_time, замените его на первое предложение WHERE:

WHERE event_time >= (SELECT event_time FROM table1 WHERE event_id = <start_id>)

Строки с тем же event_time, что и у начального ряда, но меньшим event_id будут игнорироваться.

В особом случае один прогон до конца не найден конец и строка не возвращена. COALESCE вместо этого возвращает количество всех строк.

...