52

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

CREATE TABLE measurements(
measured_at TIMESTAMPTZ,
val INTEGER
);

已经知道使用

(一个)date_trunc('hour',measured_at)

(二)generate_series

我将能够通过以下方式汇总我的数据:

microseconds,
milliseconds
.
.
.

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

我需要按不同时间分辨率聚合的数据将它们输入 FFT 或 AR 模型,以便查看可能的季节性。

4

10 回答 10

57

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

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;
于 2012-08-20T22:59:50.527 回答
16

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
于 2013-02-23T09:28:17.567 回答
13

怎么样

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

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

于 2015-03-10T03:48:02.637 回答
10

以下内容将为您提供任何大小的存储桶,即使它们与不错的分钟/小时/任何边界不一致。值“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”。

于 2015-11-02T23:52:37.743 回答
7

这是基于 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;
于 2016-06-07T16:20:30.157 回答
5

我想查看过去 24 小时的数据,并以每小时为增量进行计数。我开始使用 Cat Recall 的解决方案,它非常巧妙。但是,它与数据有关,而不仅仅是过去 24 小时内发生的事情。所以我进行了重构,最终得到了一些非常接近 Julian 的解决方案,但 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. 我并不是说这一定是最好的(甚至是一个好方法),但它是我在仪表板中运行的(至少目前)。

于 2014-12-08T18:48:28.143 回答
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) 使事情变得更简洁,尤其是当您在客户端生成查询时。

还使用内部连接摆脱了NULL之前出现的 s 之海。

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

(也出于我的目的,我添加了更多聚合字段)

于 2017-08-31T10:17:10.410 回答
3

PostgreSQL的Timescale 扩展提供了按任意时间间隔分组的能力。该函数被调用time_bucket()并具有与函数相同的语法,date_trunc()但将间隔而不是时间精度作为第一个参数。在这里你可以找到它的 API Docs。这是一个例子:

SELECT
  time_bucket('5 minutes', observation_time) as bucket,
  device_id,
  avg(metric) as metric_avg,
  max(metric) - min(metric) as metric_spread
FROM
  device_readings
GROUP BY bucket, device_id;

如果您希望使用新摄取的数据自动更新“按间隔分组”视图,并且如果您想经常查询这些视图,您也可以查看连续聚合视图。这可以为您节省大量资源,并使您的查询速度更快。

于 2020-04-16T19:50:19.407 回答
2

也许,你可以extract(epoch from measured_at)从那开始?

于 2012-08-20T21:45:34.467 回答
2

从 PostgreSQL v14 开始,您可以使用该date_bin函数:

SELECT date_bin(
          INTERVAL '5 minutes',
          measured_at,
          TIMSTAMPTZ '2000-01-01'
       ),
       sum(val)
FROM measurements
GROUP BY 1;
于 2021-07-20T14:53:12.777 回答