postgresql 将时间序列数据组合成具有基于时间差的开始-结束-间隔的数据点

yzxexxkh  于 2023-04-05  发布在  PostgreSQL
关注(0)|答案(1)|浏览(137)

描述

系统:Postgres 13 + TimescaleDB
我有一个包含设备以300秒为间隔生成的错误代码的消息的时间序列。这个时间序列应该被聚合,以便由同一设备产生的后续错误代码(在几个连续的时间间隔内)被分组,并将时间间隔相加。

源格式

ts代码装置
2023年3月1日12时00分A
2023年3月1日12时00分B
2023年3月1日12时05分B
2023-03-01 12:10:00B
2023-03-01 12:10:00B
2023-03-01 14:00:00A
2023年3月1日14时30分A

目标格式

有效区间代码装置
(2023-03-01 12:00:00,2023-03-01 12:04:59)A
(2023-03-01 12:00:00,2023-03-01 12:14:59)B
(2023-03-01 12:10:00,2023-03-01 12:14:59)B
(2023-03-01 14:00:00,2023-03-01 14:04:59)A
(2023-03-01 14:30:00,2023-03-01 14:34:59)A

进度

我试过使用[LAG()/LEAD()](https://www.postgresql.org/docs/13/functions-window.html)PARITION BY (code, device),但是我无法使用条件只聚合结果行:

SELECT ts,
       device,
       code,
       LEAD(ts) OVER (PARTITION BY device, code ORDER BY ts) as next_ts
FROM   source_format

DB-Fiddle

Schema(PostgreSQL v13)

CREATE TABLE timeseries (
      ts timestamptz,
      code bigint,
      device varchar
      );
    
    INSERT INTO timeseries VALUES ('2023-03-01 12:00:00', 1, 'A');
    INSERT INTO timeseries VALUES ('2023-03-01 12:05:00', 1, 'A');
    INSERT INTO timeseries VALUES ('2023-03-01 12:10:00', 1, 'A');
    INSERT INTO timeseries VALUES ('2023-03-01 12:10:00', 2, 'A');
    INSERT INTO timeseries VALUES ('2023-03-01 12:25:00', 1, 'A');
    INSERT INTO timeseries VALUES ('2023-03-01 12:30:00', 1, 'A');
    
    INSERT INTO timeseries VALUES ('2023-03-01 12:00:00', 1, 'B');
    INSERT INTO timeseries VALUES ('2023-03-01 12:20:00', 1, 'B');
    INSERT INTO timeseries VALUES ('2023-03-01 12:20:00', 3, 'B');
    INSERT INTO timeseries VALUES ('2023-03-01 12:25:00', 3, 'B');

查询#1(获取相同设备和代码的下一条消息的Timediff)

SELECT ts,
           device,
           code,
           LEAD(ts) OVER (PARTITION BY device, code ORDER BY ts) - ts as diff_to_next_ts
    FROM   timeseries;

预期结果

有效区间装置代码
(2023-03-01T12:00:00.000Z,2023-03-01T12:10:00.000Z)A1
(2023年3月1日12时25分00秒,2023年3月1日12时30分00秒)A1
(2023-03-01T12:10:00.000Z,2023-03-01T12:10:00.000Z)A第二章
(2023-03-01T12:00:00.000Z,2023-03-01T12:00:00.000Z)B1
(2023-03-01T12:20:00.000Z,2023-03-01T12:20:00.000Z)B1
(2023-03-01T12:20:00.000Z,2023-03-01T12:25:00.000Z)B

View on DB Fiddle
我怎样才能包含条件,然后将结果代码消息的开始和结束“合并”到一个带有间隔的单行中?有没有更合适的方法可以使用?pgSQL函数会更合适吗?

u3r8eeie

u3r8eeie1#

以下查询将生成指定的结果(时区可能不同,因为插入的时间不包括时区,但列的类型为timestamptz):

WITH labeled_ends AS (
  SELECT
    lag(ts.ts) OVER (PARTITION BY device,
      code ORDER BY ts.ts) = ts.ts - interval '5' minute IS NOT TRUE AS begins_period,
    ts.ts,
    lead(ts.ts) OVER (PARTITION BY device,
      code ORDER BY ts.ts) = ts.ts + interval '5' minute IS NOT TRUE AS ends_period,
    ts.device,
    ts.code
  FROM
    timeseries ts
),
periods AS (
  SELECT
    labeled_ends.ts,
    CASE WHEN labeled_ends.ends_period THEN
      labeled_ends.ts
    ELSE
      lead(labeled_ends.ts) OVER (PARTITION BY labeled_ends.device,
        labeled_ends.code ORDER BY labeled_ends.ts)
    END AS period_end,
    labeled_ends.device,
    labeled_ends.code,
    labeled_ends.begins_period
  FROM
    labeled_ends
  WHERE
    labeled_ends.begins_period
    OR labeled_ends.ends_period
)
SELECT
  tstzrange(periods.ts, periods.period_end, '[]') AS valid_interval,
  periods.device,
  periods.code
FROM
  periods
WHERE
  periods.begins_period
ORDER BY
  periods.device,
  periods.code,
  periods.ts;

查询的第一个CTE labeled_ends 确定时间序列中的每个消息是开始还是结束设备具有特定代码的时段。第二个CTE periods 是每个设备和代码的连续序列中最后一个消息的时间。最后一个SELECT返回连续序列开始的每个时段的范围。
考虑将封闭范围tstzrange(periods.ts, periods.period_end, '[]') AS valid_interval更改为半开放范围tstzrange(periods.ts, periods.period_end + interval '5' minute, '[)') AS valid_interval。这样做有助于使用范围运算符来确定与时间序列粒度无关的重叠和邻接。将名称更改为 valid_period 也可能更具描述性,因为 interval 通常定义为未锚定的时间长度。

相关问题