lug*_*er1 3 sql postgresql group-by average generate-series
我正在使用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
Run Code Online (Sandbox Code Playgroud)
我需要计算的平均vin1
,vin2
,vin3
300秒(5分钟)的时间间隔内.例如,从第一次(7:06:18 - 7:11:18)开始,为范围内的日期.我可以使用此查询选择我需要的数据:
select * from table
where sn='4as11111111' and date between '2018-01-01' and '2018-01-02';
Run Code Online (Sandbox Code Playgroud)
但我不知道如何用300秒的时间间隔组,并为计算平均vin1
,vin2
,vin3
对于那些5分钟间隔栏,让这样的事情:
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
Run Code Online (Sandbox Code Playgroud)
任何帮助将不胜感激.
虽然您可以使用单独的列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;
Run Code Online (Sandbox Code Playgroud)
如果日期和时间不是实际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;
Run Code Online (Sandbox Code Playgroud)
db <> 在这里小提琴
在第一个子查询中生成一个开始时间网格grid
,从给定时间范围内的第一个到最后一个限定行运行.
使用连接加入落在每个分区中的行,LATERAL
并立即在子查询中聚合平均值avg
.由于聚合,即使没有找到条目,它也总是返回一行.NULL
在这种情况下,平均值默认为 .
结果包括给定时间范围内第一个和最后一个合格行之间的所有时隙.各种其他结果组合物也是有意义的.比如包括给定时间范围内的所有时隙或仅包含具有实际值的时隙.一切皆有可能,我不得不选择一种解释.
至少有这个多列索引:
CRATE INDEX foo_idx ON tbl (sn, ts);
Run Code Online (Sandbox Code Playgroud)
或者(sn, ts, vin1, vin2, vin3)
允许仅索引扫描 - 如果满足一些前提条件,特别是如果表行比演示中宽得多.
密切相关:
根据请求并在评论中澄清,稍后在问题中再次更新以包括列mac
和loc
.我假设您需要单独的平均值(mac, loc)
.
date
并且time
仍然是单独的列,vin*列是类型float
,并且排除没有行的时隙:
更新后的查询还将set-returns函数移动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;
Run Code Online (Sandbox Code Playgroud)
创建一个多列表达式索引来支持这个:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
Run Code Online (Sandbox Code Playgroud)
db <> 在这里小提琴
但我宁愿一直使用timestamp
.