如何在 Postgres 中获取时间间隔的平均值
How to get average values for time intervals in Postgres
我正在使用 PostgreSQL 9.6。我有一个 table 这样的:
mac sn loc time date vin1 vin2 vin3
1a34 4as11111111 aaaa 7:06:18 1/1/2018 447.42 472.32 682.59
1a34 4as11111111 aaaa 7:06:43 1/1/2018 455.97 476.25 682.59
1a34 4as11111111 aaaa 7:07:35 1/1/2018 470.88 484.2 682.5
我需要计算 300 秒(5 分钟)时间间隔内 vin1
、vin2
、vin3
的平均值。例如,对于范围内的日期,从第一次 (7:06:18 - 7:11:18) 开始。我可以 select 通过此查询获得我需要的数据:
select * from table
where sn='4as11111111' and date between '2018-01-01' and '2018-01-02';
但我不知道如何按 300 秒的时间间隔对其进行分组并计算这 5 分钟间隔的 vin1
、vin2
、vin3
列的平均值,以获得像这样:
mac sn loc time date vin1_av vin2_av vin3_av
1a34 4as11111111 aaaa 7:06:18 1/1/2018 450.0 480.32 600.59
1a34 4as11111111 aaaa 7:11:18 1/1/2018 460.0 490.25 782.59
1a34 4as11111111 aaaa 7:16:18 1/1/2018 470.88 500.2 600.5
如有任何帮助,我们将不胜感激。
数据库设计
虽然您 可以 使用单独的 date
和 time
列,但与单个 timestamp
列相比确实没有任何优势。我会适应:
ALTER TABLE tbl ADD column ts timestamp;
UPDATE tbl SET ts = date + time; -- assuming actual date and time types
ALTER TABLE tbl DROP column date, DROP column time;
如果日期和时间不是实际的 date
和 time
数据类型,请使用 to_timestamp()
。相关:
- Calculating Cumulative Sum in PostgreSQL
- How to convert "string" to "timestamp without time zone"
查询
那么查询就简单一点:
SELECT *
FROM (
SELECT sn, generate_series(min(ts), max(ts), interval '5 min') AS ts
FROM tbl
WHERE sn = '4as11111111'
AND ts >= '2018-01-01'
AND ts < '2018-01-02'
GROUP BY 1
) grid
CROSS JOIN LATERAL (
SELECT round(avg(vin1), 2) AS vin1_av
, round(avg(vin2), 2) AS vin2_av
, round(avg(vin3), 2) AS vin3_av
FROM tbl
WHERE sn = grid.sn
AND ts >= grid.ts
AND ts < grid.ts + interval '5 min'
) avg;
db<>fiddle here
在第一个子查询 grid
、运行 中从给定时间范围内的第一个到最后一个 qualifying 行生成开始时间网格。
使用 LATERAL
连接连接到每个分区中的行,并立即在子查询 avg
中聚合平均值。由于聚合,它 总是 returns 一行,即使没有找到任何条目。在这种情况下,平均值默认为 NULL
。
结果包括给定时间范围内第一行和最后一行之间的所有时隙。其他各种结果组合也很有意义。就像在给定时间范围内包括 all 个时间段或仅包含实际值的时间段。所有可能,我不得不选择一种解释。
索引
至少要有这个多列索引:
CRATE INDEX foo_idx ON tbl (sn, ts);
或在 (sn, ts, vin1, vin2, vin3)
上允许仅索引扫描 - 如果满足某些先决条件,尤其是如果 table 行比演示中的要宽得多。
密切相关:
- Best way to count records by arbitrary time intervals in Rails+Postgres
基于您原来的 table
,后来在问题中再次更新以包含列 mac
和 loc
。我假设您想要每个 (mac, loc)
.
的单独平均值
date
和time
还是分开的列,vin*列是float
类型,排除没有行的时隙:
更新后的查询还将集合返回函数 generate_series()
移动到 FROM
列表,在 Postgres 10 之前更干净:
SELECT t.mac, sn.sn, t.loc, ts.ts::time AS time, ts.ts::date AS date
, t.vin1_av, t.vin2_av, t.vin3_av
FROM (SELECT text '4as11111111') sn(sn) -- provide sn here once
CROSS JOIN LATERAL (
SELECT min(date+time) AS min_ts, max(date+time) AS max_ts
FROM tbl
WHERE sn = sn.sn
AND date+time >= '2018-01-01 0:0' -- provide time frame here
AND date+time < '2018-01-02 0:0'
) grid
CROSS JOIN LATERAL generate_series(min_ts, max_ts, interval '5 min') ts(ts)
CROSS JOIN LATERAL (
SELECT mac, loc
, round(avg(vin1)::numeric, 2) AS vin1_av -- cast to numeric for round()
, round(avg(vin2)::numeric, 2) AS vin2_av -- but rounding is optional
, round(avg(vin3)::numeric, 2) AS vin3_av
FROM tbl
WHERE sn = sn.sn
AND date+time >= ts.ts
AND date+time < ts.ts + interval '5 min'
GROUP BY mac, loc
HAVING count(*) > 0 -- exclude empty slots
) t;
创建多列表达式索引以支持此:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle here
但我更愿意一直使用 timestamp
。
我正在使用 PostgreSQL 9.6。我有一个 table 这样的:
mac sn loc time date vin1 vin2 vin3
1a34 4as11111111 aaaa 7:06:18 1/1/2018 447.42 472.32 682.59
1a34 4as11111111 aaaa 7:06:43 1/1/2018 455.97 476.25 682.59
1a34 4as11111111 aaaa 7:07:35 1/1/2018 470.88 484.2 682.5
我需要计算 300 秒(5 分钟)时间间隔内 vin1
、vin2
、vin3
的平均值。例如,对于范围内的日期,从第一次 (7:06:18 - 7:11:18) 开始。我可以 select 通过此查询获得我需要的数据:
select * from table
where sn='4as11111111' and date between '2018-01-01' and '2018-01-02';
但我不知道如何按 300 秒的时间间隔对其进行分组并计算这 5 分钟间隔的 vin1
、vin2
、vin3
列的平均值,以获得像这样:
mac sn loc time date vin1_av vin2_av vin3_av
1a34 4as11111111 aaaa 7:06:18 1/1/2018 450.0 480.32 600.59
1a34 4as11111111 aaaa 7:11:18 1/1/2018 460.0 490.25 782.59
1a34 4as11111111 aaaa 7:16:18 1/1/2018 470.88 500.2 600.5
如有任何帮助,我们将不胜感激。
数据库设计
虽然您 可以 使用单独的 date
和 time
列,但与单个 timestamp
列相比确实没有任何优势。我会适应:
ALTER TABLE tbl ADD column ts timestamp;
UPDATE tbl SET ts = date + time; -- assuming actual date and time types
ALTER TABLE tbl DROP column date, DROP column time;
如果日期和时间不是实际的 date
和 time
数据类型,请使用 to_timestamp()
。相关:
- Calculating Cumulative Sum in PostgreSQL
- How to convert "string" to "timestamp without time zone"
查询
那么查询就简单一点:
SELECT *
FROM (
SELECT sn, generate_series(min(ts), max(ts), interval '5 min') AS ts
FROM tbl
WHERE sn = '4as11111111'
AND ts >= '2018-01-01'
AND ts < '2018-01-02'
GROUP BY 1
) grid
CROSS JOIN LATERAL (
SELECT round(avg(vin1), 2) AS vin1_av
, round(avg(vin2), 2) AS vin2_av
, round(avg(vin3), 2) AS vin3_av
FROM tbl
WHERE sn = grid.sn
AND ts >= grid.ts
AND ts < grid.ts + interval '5 min'
) avg;
db<>fiddle here
在第一个子查询 grid
、运行 中从给定时间范围内的第一个到最后一个 qualifying 行生成开始时间网格。
使用 LATERAL
连接连接到每个分区中的行,并立即在子查询 avg
中聚合平均值。由于聚合,它 总是 returns 一行,即使没有找到任何条目。在这种情况下,平均值默认为 NULL
。
结果包括给定时间范围内第一行和最后一行之间的所有时隙。其他各种结果组合也很有意义。就像在给定时间范围内包括 all 个时间段或仅包含实际值的时间段。所有可能,我不得不选择一种解释。
索引
至少要有这个多列索引:
CRATE INDEX foo_idx ON tbl (sn, ts);
或在 (sn, ts, vin1, vin2, vin3)
上允许仅索引扫描 - 如果满足某些先决条件,尤其是如果 table 行比演示中的要宽得多。
密切相关:
- Best way to count records by arbitrary time intervals in Rails+Postgres
基于您原来的 table
mac
和 loc
。我假设您想要每个 (mac, loc)
.
date
和time
还是分开的列,vin*列是float
类型,排除没有行的时隙:
更新后的查询还将集合返回函数 generate_series()
移动到 FROM
列表,在 Postgres 10 之前更干净:
SELECT t.mac, sn.sn, t.loc, ts.ts::time AS time, ts.ts::date AS date
, t.vin1_av, t.vin2_av, t.vin3_av
FROM (SELECT text '4as11111111') sn(sn) -- provide sn here once
CROSS JOIN LATERAL (
SELECT min(date+time) AS min_ts, max(date+time) AS max_ts
FROM tbl
WHERE sn = sn.sn
AND date+time >= '2018-01-01 0:0' -- provide time frame here
AND date+time < '2018-01-02 0:0'
) grid
CROSS JOIN LATERAL generate_series(min_ts, max_ts, interval '5 min') ts(ts)
CROSS JOIN LATERAL (
SELECT mac, loc
, round(avg(vin1)::numeric, 2) AS vin1_av -- cast to numeric for round()
, round(avg(vin2)::numeric, 2) AS vin2_av -- but rounding is optional
, round(avg(vin3)::numeric, 2) AS vin3_av
FROM tbl
WHERE sn = sn.sn
AND date+time >= ts.ts
AND date+time < ts.ts + interval '5 min'
GROUP BY mac, loc
HAVING count(*) > 0 -- exclude empty slots
) t;
创建多列表达式索引以支持此:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle here
但我更愿意一直使用 timestamp
。