我正在使用PostgreSQL 9.6。我有一张这样的桌子:
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 * 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
非常感谢任何帮助。
答案 0 :(得分: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()
。相关:
然后查询更简单:
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&lt;&gt;小提琴here
在第一个子查询grid
中生成一个开始时间网格,在给定时间范围内从第一个限定行开始运行。
使用LATERAL
联接加入属于每个分区的行,并立即在子查询avg
中聚合平均值。由于聚合,即使没有找到条目,始终也会返回一行。在这种情况下,平均值默认为NULL
。
结果包括给定时间范围内第一个和最后一个合格行之间的所有时间段。各种其他结果组合物也是有意义的。比如在给定时间范围内包含所有次时隙或仅包含具有实际值的时隙。一切皆有可能,我不得不选择一种解释。
至少有这个多列索引:
CRATE INDEX foo_idx ON tbl (sn, ts);
或者在(sn, ts, vin1, vin2, vin3)
上允许仅索引扫描 - 如果满足某些先决条件,特别是如果表行比演示中宽得多。
密切相关:
As requested and clarified in the comment,稍后在问题中再次更新,以包含列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&lt;&gt;小提琴here
但我宁愿一直使用timestamp
。