Как объединить данные журнала состояний во временные интервалы с одинаковым состоянием в PostgreSQL? - PullRequest
0 голосов
/ 28 августа 2018

У меня есть журнал состояний объектов, который выглядит следующим образом

timestamp, object_id, state, level
2018-01-01 123        f      100 
2018-01-02 123        t      100    
2018-01-02 123        f      100
2018-01-03 123        f      100
2018-01-03 123        f      100
2018-01-06 123        t      90
2018-01-07 123        t      90
2018-01-08 123        f      90

Отметка времени фактически является полной датой / временем, для краткости я не включил компонент времени.

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

start      end        object_id, state, level
2018-01-01 2018-01-02 123        f      100 
2018-01-02 2018-01-02 123        t      100
2018-01-02 2018-01-06 123        f      100
2018-01-06 2018-01-08 123        t      90
2018-01-08 NOW()      123        f      90

Я пытался придумать способ сделать это с помощью оконных функций, что-то вроде

SELECT
    timestamp,
    object_id,
    timestamp as start,
    lead(timestamp) OVER (ORDER BY timestamp) as end,
FROM (
    SELECT
        timestamp,
        object_id,
        state,
        evel,
        rank() OVER (PARTITION BY (state, level) ORDER BY timestamp) as rank
    FROM state_log AS l
    WHERE object_id=123 AND timestamp >= DATE '2018-01-01'
    ORDER BY timestamp
) AS states
WHERE rank=1

Но я думаю, что не понимаю, как работает rank (), и он не делает то, что мне нужно. По какой-то причине я думал, что rank () будет сбрасывать количество строк при каждом изменении раздела, но это не так. Как мне это сделать?

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Речь идет не о "пропастях и островах". Этот метод работает с группами с последовательными значениями констант некоторого поля, но вам нужно работать с границами таких групп. Итак:

create table state_log(timestamp timestamp, object_id int, state boolean, level int);
insert into state_log values
  ('2018-01-01 00:00:01', 123, 'f', 100),
  ('2018-01-02 00:00:02', 123, 't', 100),
  ('2018-01-02 00:00:03', 123, 'f', 100),
  ('2018-01-03 00:00:04', 123, 'f', 100),
  ('2018-01-03 00:00:05', 123, 'f', 100),
  ('2018-01-06 00:00:06', 123, 't', 90),
  ('2018-01-07 00:00:07', 123, 't', 90),
  ('2018-01-08 00:00:08', 123, 'f', 90);

select
  timestamp::date as start,
  coalesce(lead(timestamp) over (order by timestamp), now()::timestamp)::date as end,
  object_id, state, level
from (
  select 
    *,
    coalesce(lag(state) over (order by timestamp) <> state, true) as is_new_group
  from state_log) as t
where
  object_id = 123 and timestamp >= date '2018-01-01' and
  is_new_group
order by timestamp;

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

┌────────────┬────────────┬───────────┬───────┬───────┐
│   start    │    end     │ object_id │ state │ level │
├────────────┼────────────┼───────────┼───────┼───────┤
│ 2018-01-01 │ 2018-01-02 │       123 │ f     │   100 │
│ 2018-01-02 │ 2018-01-02 │       123 │ t     │   100 │
│ 2018-01-02 │ 2018-01-06 │       123 │ f     │   100 │
│ 2018-01-06 │ 2018-01-08 │       123 │ t     │    90 │
│ 2018-01-08 │ 2018-08-30 │       123 │ f     │    90 │
└────────────┴────────────┴───────────┴───────┴───────┘
0 голосов
/ 29 августа 2018

Это проблема пробелов и островков. Хорошее решение использует row_number():

select object_id, level, state, min(timestamp), max(timestamp)
from (select t.*,
             row_number() over (partition by object_id, level order by timestamp) as seqnum,
             row_number() over (partition by object_id, level, state order by timestamp) as seqnum_2
      from t
     ) t
group by (seqnum - seqnum_2), object_id, level, state;

Немного сложно объяснить, почему это работает. Но если вы посмотрите на результаты подзапроса, вы увидите, что разница между двумя seqnum s постоянна, когда состояние постоянно. Это определяет желаемую группировку - вместе с другими столбцами - так что остальное - просто агрегация.

Вот rextester , показывающий его работоспособность.

...