根据时间戳范围分解(重复)记录 (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
来电。可能是 cross
或 lateral
自连接...
目前正在使用 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);
我正在尝试将时间序列记录集转换为更适合数据分析的内容。考虑以下连续记录集 (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
来电。可能是 cross
或 lateral
自连接...
目前正在使用 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);