根据时间戳范围分解(重复)记录 (PostgreSQL)

Explode (duplicate) records based on timestamp range (PostgreSQL)

我正在尝试将时间序列记录集转换为更适合数据分析的内容。考虑以下连续记录集 (From, To, Value)

"2019-10-03 03:58:21+00"    "2019-10-03 03:59:04+00"    10
"2019-10-03 03:59:04+00"    "2019-10-03 03:59:54+00"    15
"2019-10-03 03:59:54+00"    "2019-10-03 04:02:00+00"    20
"2019-10-03 04:02:00+00"    "2019-10-03 04:02:10+00"    25

我想要在每分钟开始时记录一条记录,但代价是“重复”行。

"2019-10-03 03:58:21+00"    "2019-10-03 03:59:00+00"    10
"2019-10-03 03:59:00+00"    "2019-10-03 03:59:04+00"    10
"2019-10-03 03:59:04+00"    "2019-10-03 03:59:54+00"    15
"2019-10-03 03:59:54+00"    "2019-10-03 04:00:00+00"    20
"2019-10-03 04:00:00+00"    "2019-10-03 04:01:00+00"    20
"2019-10-03 04:01:00+00"    "2019-10-03 04:02:00+00"    20
"2019-10-03 04:02:00+00"    "2019-10-03 04:02:10+00"    25

第一行重复了一次,因为它重叠了一分钟。第二个没有重复,因为它在一分钟内。 Third 被复制了两次,因为它重叠了 3 分钟。 Last 没有爆炸,因为它在一分钟内(但它也在一分钟内开始)。该值本身在展开的行中保持不变。

感觉我的解决方案是Window函数(lead/lag?)、generate_series()和各种date_part的组合/ date_trunc 来电。可能是 crosslateral 自连接...

目前正在使用 PostgreSQL 13.4,所以我应该可以访问最新最好的 API。我也在使用 timescaledb 2.4.2 和 hypertables(如果有任何帮助的话),尽管它们的 time_bucket 功能似乎更多地是关于减少行数,而不是增加行数。

希望朝着正确的方向前进!

如我所料,它是 generate_series 和 window 函数的组合。但是我没想到必须创建自己的 locf 函数,我认为 LEAD/LAG 可以选择记住最后一个已知/非空值。

以下代码采用一些已知记录,并将它们与生成的一系列时间戳结合起来。

我需要使用 DISTINCT ON 来清除已生成的已知等价物的记录。

然后我终于可以使用 LEAD 作为“下一个日期”和 locf_any 作为结转值。

--https://www.joyofdata.de/blog/locf-linear-imputation-postgresql-tutorial/

DROP FUNCTION locf_s(ANYELEMENT, ANYELEMENT) CASCADE;
CREATE OR REPLACE FUNCTION locf_s(a ANYELEMENT, b ANYELEMENT)
RETURNS ANYELEMENT
LANGUAGE sql
AS '
  SELECT COALESCE(b, a)
';

DROP AGGREGATE IF EXISTS locf_any(ANYELEMENT);
CREATE AGGREGATE locf_any(ANYELEMENT) (
  SFUNC = locf_s,
  STYPE = ANYELEMENT
);

SELECT from_time, LEAD(from_time) OVER W, locf_any(reading) OVER W
FROM
(
  SELECT DISTINCT ON (from_time) from_time, reading FROM
  (
    WITH readings (from_time, reading) AS (VALUES
     ('2019-10-03 03:58:21+00', 10),
     ('2019-10-03 03:59:04+00', 15),
     ('2019-10-03 03:59:54+00', 20),
     ('2019-10-03 04:02:00+00', 25)
    )
    (
      SELECT from_time::TIMESTAMPTZ, reading::INTEGER FROM readings
      UNION ALL
      SELECT generate_series('2019-10-03 03:59:00+00', '2019-10-03 04:04:00+00', '1 minute'::INTERVAL)::TIMESTAMPTZ, NULL
    )
  ) X
  ORDER BY from_time, reading NULLS LAST
) Y
WINDOW W AS (ORDER BY from_time ASC);