Postgresql SQL GROUP BY具有任意精度的时间间隔(小至毫秒)

我有我的测量数据存储到以下结构:

CREATE TABLE measurements( measured_at TIMESTAMPTZ, val INTEGER ); 

已经知道使用

(a) date_trunc('hour',measured_at)

(b) generate_series

我将能够汇总我的数据:

 microseconds, milliseconds . . . 

但是有可能将数据汇总5分钟,或者说是任意数量的秒数? 是否可以将测量数据汇总到任意倍数?

我需要通过不同时间分辨率汇总的数据,将它们input到FFT或AR模型中,以便查看可能的季节性。

您可以通过添加由generate_series()创build的时间间隔来生成“桶”表。 这个SQL语句会在您的数据中为第一天生成一个五分钟桶的表( min(measured_at)的值min(measured_at)

 select (select min(measured_at)::date from measurements) + ( n || ' minutes')::interval start_time, (select min(measured_at)::date from measurements) + ((n+5) || ' minutes')::interval end_time from generate_series(0, (24*60), 5) n 

将该语句包装在一个公共表格expression式中,并且可以像在基表中一样join和分组。

 with five_min_intervals as ( select (select min(measured_at)::date from measurements) + ( n || ' minutes')::interval start_time, (select min(measured_at)::date from measurements) + ((n+5) || ' minutes')::interval end_time from generate_series(0, (24*60), 5) n ) select f.start_time, f.end_time, avg(m.val) avg_val from measurements m right join five_min_intervals f on m.measured_at >= f.start_time and m.measured_at < f.end_time group by f.start_time, f.end_time order by f.start_time 

按任意秒数分组是相似的 – 使用date_trunc()


generate_series()的更广泛使用可以避免猜测五分钟桶的上限。 在实践中,您可能会将其作为视图或函数来构build。 您可能会从基表获得更好的性能。

 select (select min(measured_at)::date from measurements) + ( n || ' minutes')::interval start_time, (select min(measured_at)::date from measurements) + ((n+5) || ' minutes')::interval end_time from generate_series(0, ((select max(measured_at)::date - min(measured_at)::date from measurements) + 1)*24*60, 5) n; 

Catcall有一个很好的答案。 我使用它的例子表明有固定的桶 – 在这种情况下从午夜开始30分钟。 这也表明在Catcall的第一个版本中可以生成一个额外的桶,以及如何消除它。 我一天只需要48桶。 在我的问题中,观察具有单独的date和时间列,我想在一个30分钟的时间范围内对一些不同的服务进行平均观测。

 with intervals as ( select (n||' minutes')::interval as start_time, ((n+30)|| ' minutes')::interval as end_time from generate_series(0, (23*60+30), 30) n ) select i.start_time, o.service, avg(oo) from observations o right join intervals i on o.time >= i.start_time and o.time < i.end_time where o.date between '2013-01-01' and '2013-01-31' group by i.start_time, i.end_time, o.service order by i.start_time 

怎么样

 SELECT MIN(val), EXTRACT(epoch FROM measured_at) / EXTRACT(epoch FROM INTERVAL '5 min') AS int FROM measurements GROUP BY int 

其中“5分钟”可以是由INTERVAL支持的任何expression

以下将给你任何规模的水桶,即使他们没有良好的分钟/小时/无论什么边界都很好。 值“300”是一个5分钟的分组,但任何值都可以被replace:

 select measured_at, val, (date_trunc('seconds', (measured_at - timestamptz 'epoch') / 300) * 300 + timestamptz 'epoch') as aligned_measured_at from measurements; 

然后,您可以使用围绕“val”的任何聚合,并根据需要使用“group by aligned_measured_at”。

我想查看过去24小时的数据,并以小时为单位进行计算。 我开始了Cat Recall的解决scheme,这非常漂亮。 不过,这与数据有关,而不仅仅是过去24小时发生的事情。 所以我重构了一些非常接近Julian解决scheme的东西,但是CTE更多。 所以这就是2个答案的结合。

 WITH interval_query AS ( SELECT (ts ||' hour')::INTERVAL AS hour_interval FROM generate_series(0,23) AS ts ), time_series AS ( SELECT date_trunc('hour', now()) + INTERVAL '60 min' * ROUND(date_part('minute', now()) / 60.0) - interval_query.hour_interval AS start_time FROM interval_query ), time_intervals AS ( SELECT start_time, start_time + '1 hour'::INTERVAL AS end_time FROM time_series ORDER BY start_time ), reading_counts AS ( SELECT f.start_time, f.end_time, br.minor, count(br.id) readings FROM beacon_readings br RIGHT JOIN time_intervals f ON br.reading_timestamp >= f.start_time AND br.reading_timestamp < f.end_time AND br.major = 4 GROUP BY f.start_time, f.end_time, br.minor ORDER BY f.start_time, br.minor ) SELECT * FROM reading_counts 

请注意,任何额外的限制,我想在最后的查询需要在RIGHT JOIN 。 我并不是说这一定是最好的(甚至是一个好的方法),但是这是我在仪表板中运行的东西(至less现在是这样)。

这是基于Mike Sherrill的回答,除了它使用时间戳间隔而不是单独的开始/结束列。

 with intervals as ( select tstzrange(s, s + '5 minutes') das_interval from (select generate_series(min(lower(time_range)), max(upper(time_rage)), '5 minutes') s from your_table) x) select das_interval, your_tabe.* from your_table right join intervals on time_range && das_interval order by das_interval; 

也许,你可以extract(epoch from measured_at)并从那里?

我已经综合了以上所有内容来尝试使用稍微容易一些的东西,

 create or replace function interval_generator(start_ts timestamp with TIME ZONE, end_ts timestamp with TIME ZONE, round_interval INTERVAL) returns TABLE(start_time timestamp with TIME ZONE, end_time timestamp with TIME ZONE) as $$ BEGIN return query SELECT (n) start_time, (n + round_interval) end_time FROM generate_series(date_trunc('minute', start_ts), end_ts, round_interval) n; END $$ LANGUAGE 'plpgsql'; 

这个函数是Mikes答案的时间戳抽象,这个(IMO)让事情变得更简洁,特别是当你在客户端产生查询的时候。

同样使用内部连接也可以摆脱之前出现的NULL的海洋。

 with intervals as (select * from interval_generator(NOW() - INTERVAL '24 hours' , NOW(), '30 seconds'::INTERVAL)) select f.start_time, m.session_id, m.metric, min(m.value) min_val, avg(m.value) avg_val, max(m.value) max_val from ts_combined as m inner JOIN intervals f on m.time >= f.start_time and m.time < f.end_time GROUP BY f.start_time, f.end_time, m.metric, m.session_id ORDER BY f.start_time desc 

(也为了我的目的,我添加了几个聚合字段)