【问题标题】:Aggregate values over a range of hours, every hour每小时汇总一个小时范围内的值
【发布时间】:2012-10-21 02:15:12
【问题描述】:

我有一个 PostgreSQL 9.1 数据库,其中包含一个表,其中包含 时间戳 和测量

'2012-10-25 01:00'   2
'2012-10-25 02:00'   5
'2012-10-25 03:00'   12
'2012-10-25 04:00'   7
'2012-10-25 05:00'   1
...                  ...

我需要在 8 小时的范围内平均每个小时的值。换句话说,我需要 1h-8h、2h-9h、3h-10h 等的平均值。

我不知道如何进行这样的查询。我到处寻找,但也不知道要寻找什么功能。

我发现的收盘价是每小时/每日平均值或区块平均值(例如 1h-8h、9h-16h 等)。但在这些情况下,时间戳只是使用date_trunc() 函数进行转换(如下例所示),这对我没有用处。

我认为我正在寻找的是类似于此的功能

SELECT    date_trunc('day', timestamp), max(value) 
FROM      table_name
GROUP BY  date_trunc('day', timestamp);

然后在 group-by 子句中使用某种 8 小时范围来表示每个小时。这可能吗?

【问题讨论】:

    标签: sql postgresql datetime postgresql-9.1 window-functions


    【解决方案1】:

    window function with a custom frame 让这非常简单

    SELECT ts
          ,avg(val) OVER (ORDER BY ts
                          ROWS BETWEEN CURRENT ROW AND 7 FOLLOWING) AS avg_8h
    FROM tbl;
    

    Live demo on sqlfiddle.

    每个平均值的框架是当前行加上以下 7 行。这假设您每小时恰好有一行。您的示例数据似乎暗示了这一点,但您没有具体说明。

    就是这样,avg_8h 为最后(根据ts)集合的 7 行用更少的行计算,直到最后一行的值等于它自己的平均值。您没有指定如何处理这种特殊情况。

    【讨论】:

    • 非常有用的解决方案!非常感谢。奇迹般有效。稍作更改以获得 7 个 preceding 行:SELECT ts, avg(value) over (ORDER BY ts ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS avg_8h FROM table;
    • 为了处理开头的特殊“边界情况”,我在另一个 SELECT 语句中使用带有窗口函数的 SELECT 语句 - 从而切断这些不正确的值:SELECT temp.* FROM (SELECT ts, avg(value) over (ORDER BY ts ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS avg_8h FROM table WHERE ts > [some date] - interval '8 hour') as temp WHERE temp.ts > [some date]; 有没有更好的解决方案到这个?
    【解决方案2】:

    关键是创建一个虚拟表来加入您的结果集。 generate_series 函数可以通过以下方式帮助做到这一点:

    SELECT
        start
        , start + interval '8 hours' as end
    FROM (
        SELECT generate_series(
            date'2012-01-01'
            , date'2012-02-02'
            , '1 hour'
        ) AS start
    ) x;
    

    这会产生类似这样的输出:

             start          |          end           
    ------------------------+------------------------
     2012-01-01 00:00:00+00 | 2012-01-01 08:00:00+00
     2012-01-01 01:00:00+00 | 2012-01-01 09:00:00+00
     2012-01-01 02:00:00+00 | 2012-01-01 10:00:00+00
     2012-01-01 03:00:00+00 | 2012-01-01 11:00:00+00
    

    这为您提供了将数据加入其中的内容。这样,查询如下:

    SELECT
        y.start
        , round(avg(ts_val.v))
    FROM
        ts_val,
        (
            SELECT
                start
                , start + interval '8 hours' as end
            FROM (
                SELECT generate_series(
                    date'2012-01-01'
                    , date'2012-02-02'
                    , '1 hour'
                ) AS start
            ) x
        ) y
    WHERE
        ts BETWEEN y.start AND y.end
    GROUP BY
        y.start
    ORDER BY
        y.start
    ;
    

    对于以下数据

             ts          | v 
    ---------------------+---
     2012-01-01 01:00:00 | 2
     2012-01-01 09:00:00 | 2
     2012-01-01 10:00:00 | 5
    (3 rows)
    

    会产生以下结果:

             start          | round 
    ------------------------+-------
     2012-01-01 00:00:00+00 |   2.0
     2012-01-01 01:00:00+00 |   2.0
     2012-01-01 02:00:00+00 |   3.5
     2012-01-01 03:00:00+00 |   3.5
     2012-01-01 04:00:00+00 |   3.5
     2012-01-01 05:00:00+00 |   3.5
     2012-01-01 06:00:00+00 |   3.5
     2012-01-01 07:00:00+00 |   3.5
     2012-01-01 08:00:00+00 |   3.5
     2012-01-01 09:00:00+00 |   3.5
     2012-01-01 10:00:00+00 |   5.0
    (11 rows)
    

    【讨论】:

    • 您可能对我发布的带有窗口功能的简单版本感兴趣。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多