如何获得Postgres中时间间隔的平均值

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,vin3300秒(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)

任何帮助将不胜感激.

Erw*_*ter 5

数据库设计

虽然您可以使用单独的列datetime列,但实际上没有优势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)

如果日期和时间不是实际datetime数据类型,请使用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)允许仅索引扫描 - 如果满足一些前提条件,特别是如果表行比演示中宽得多.

密切相关:

基于您的原始表

根据请求并在评论中澄清,稍后在问题中再次更新以包括列macloc.我假设您需要单独的平均值(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.