数据库设计
当你can与单独的工作date
and time
柱子,确实比单个柱子没有优势timestamp https://www.postgresql.org/docs/current/datatype-datetime.html柱子。我会适应:
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
and time
数据类型、使用to_timestamp()
。有关的:
- 在 PostgreSQL 中计算累积和 https://stackoverflow.com/questions/22841206/calculating-cumulative-sum-in-postgresql/22843199#22843199
- 如何将“字符串”转换为“没有时区的时间戳” https://stackoverflow.com/questions/18913236/how-to-convert-string-to-timestamp-without-time-zone/18919571#18919571
Query
那么查询就简单了一些:
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;
数据库小提琴
在第一个子查询中生成开始时间网格grid
,从第一个运行到最后一个排位赛给定时间范围内的行。
使用 a 连接到每个分区中的行LATERAL
连接并立即聚合子查询中的平均值avg
。由于聚集体,它always即使未找到任何条目,也会返回一行。平均值默认为NULL
在这种情况下。
结果包括给定时间范围内第一个和最后一个合格行之间的所有时间段。其他各种结果组合也有意义。喜欢包括all给定时间范围内的时间段或仅具有实际值的时间段。一切可能,我必须选择一种解释。
Index
至少有这个多列索引:
CRATE INDEX foo_idx ON tbl (sn, ts);
Or on (sn, ts, vin1, vin2, vin3)
允许仅索引扫描 - 如果满足一些先决条件,特别是如果表行比演示中宽得多。
密切相关:
- CTE 上具有时间间隔的慢速 LEFT JOIN https://stackoverflow.com/questions/50221842/slow-left-join-on-cte-with-time-intervals/50224911?noredirect=1#comment87491850_50224911
- 按任意时间间隔计算行数的最佳方法 https://stackoverflow.com/questions/15576794/best-way-to-count-records-by-arbitrary-time-intervals-in-railspostgres/15577413#15577413
根据您的原始表
按照评论中的要求和澄清 https://stackoverflow.com/questions/50261637/how-to-get-average-values-for-time-intervals-in-postgres/50264875?noredirect=1#comment87571181_50264875,后来在问题中再次更新以包含这些列mac
and loc
。我假设你想要每个单独的平均值(mac, loc)
.
date
and time
仍然是单独的列,vin* 列是类型float
,并排除没有行的时间段:
更新后的查询还移动了设置返回函数generate_series()
to the 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));
数据库小提琴
但我更愿意使用timestamp
一直。