Postgresql SQL GROUP BY时间间隔,任意精度(低至毫秒)

问题描述 投票:36回答:8

我将测量数据存储到以下结构中:

CREATE TABLE measurements(
measured_at TIMESTAMPTZ,
val INTEGER
);

我已经知道使用了

(a)date_trunc('hour',measured_at)

(b)generate_series

我可以通过以下方式汇总我的数据:

microseconds,
milliseconds
.
.
.

但是有可能将数据聚合5分钟,或者说是任意秒数吗?是否可以将测量数据聚合为任意秒数?

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

postgresql group-by
8个回答
43
投票

您可以通过添加generate_series()创建的间隔来生成“桶”表。此SQL语句将在数据中生成第一天(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

将该语句包装在公用表表达式中,您可以将其加入并分组,就像它是基表一样。

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()可以避免猜测五分钟存储桶的上限。在实践中,您可能将其构建为视图或函数。您可以从基表获得更好的性能。

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;

12
投票

Catcall有一个很好的答案。我使用它的例子演示了固定存储桶 - 在这种情况下,从午夜开始每隔30分钟。它还表明在Catcall的第一个版本中可以生成一个额外的存储桶以及如何消除它。我一天只需要48个桶。在我的问题中,观察具有单独的日期和时间列,并且我希望在一个月内的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(o.o)
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

9
投票

怎么样

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

其中'5分钟'可以是INTERVAL支持的任何表达式


8
投票

以下内容将为您提供任何尺寸的水桶,即使它们没有很好的分钟/小时/任何边界。值“300”用于5分钟分组,但任何值都可以替换:

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”。


6
投票

这是基于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_table.*
from   your_table
right join intervals on time_range && das_interval
order by das_interval;

5
投票

我想查看过去24小时的数据,并以小时为单位计算数据。我开始使用Cat Recall的解决方案,它很漂亮。但这与数据有关,而不仅仅是过去24小时发生的事情。所以我重构并最终得到了一些非常接近朱利安解决方案的东西,但有更多的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中完成。我并不是说这必然是最好的(甚至是一种好的方法),但它是我在仪表板中运行的(至少目前)。


3
投票

我已经对上述所有内容进行了综合,试图提出一些稍微容易使用的东西;

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 answer的时间戳抽象,它(IMO)使事情变得更清晰,特别是如果你在客户端生成查询。

同样使用内部联接摆脱了先前出现的NULLs海。

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

(另外为了我的目的,我在几个聚合字段中添加了)


1
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.