27

我的应用程序有一个Events带有时间戳事件的表。

我需要报告每个最近N时间间隔内的事件计数。对于不同的报告,间隔可以是“每周”或“每天”或“每小时”或“每 15 分钟间隔”。

例如,用户可以显示他们每周、每天、每小时或每一刻钟收到了多少订单。

1) 我的偏好是动态地执行一个按任意时间间隔分组的 SQL 查询(我正在使用 Postgres)。有没有办法做到这一点?

2)一个简单但丑陋的蛮力方法是对按时间戳排序的开始/结束时间范围内的所有记录进行一次查询,然后有一个方法手动建立一个按任何时间间隔的计数。

3)另一种方法是为每个间隔向事件表添加单独的字段并静态存储一个the_week the_day,the_hourthe_quarter_hour字段,因此我在创建记录时(一次)而不是每次我报告该字段时都接受“命中” .

鉴于我可以修改模型并在需要时预存储间隔数据(尽管以将表宽度加倍的适度费用),这里的最佳实践是什么?

4

1 回答 1

51

幸运的是,您使用的是 PostgreSQL。窗口函数generate_series()是你的朋友。

测试用例

给定以下测试表(应该提供):

CREATE TABLE event(event_id serial, ts timestamp);
INSERT INTO event (ts)
SELECT generate_series(timestamp '2018-05-01'
                     , timestamp '2018-05-08'
                     , interval '7 min') + random() * interval '7 min';

每 7 分钟一个事件(加上 0 到 7 分钟,随机)。

基本解决方案

此查询计算任意时间间隔的事件。示例中的 17 分钟:

WITH grid AS (
   SELECT start_time
        , lead(start_time, 1, 'infinity') OVER (ORDER BY start_time) AS end_time
   FROM  (
      SELECT generate_series(min(ts), max(ts), interval '17 min') AS start_time
      FROM   event
      ) sub
   )
SELECT start_time, count(e.ts) AS events
FROM   grid       g
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.end_time
GROUP  BY start_time
ORDER  BY start_time;

该查询从基表中检索最小值和最大值ts以覆盖整个时间范围。您可以改用任意时间范围。

根据需要提供任何时间间隔

为每个时隙生成一行。如果在该时间间隔内没有发生任何事件,则计数为0

请务必正确处理上限和下限。看:

窗口函数lead()有一个经常被忽视的特性:它可以在不存在前导行时提供默认值。'infinity'在示例中提供。否则最后一个区间将被一个上限截断NULL

最小当量

上述查询使用 CTElead()和详细语法。优雅,也许更容易理解,但有点贵。这是一个更短、更快、最小的版本:

SELECT start_time, count(e.ts) AS events
FROM  (SELECT generate_series(min(ts), max(ts), interval '17 min') FROM event) g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '17 min'
GROUP  BY 1
ORDER  BY 1;

“过去一周每 15 分钟”的示例`

格式化为to_char().

SELECT to_char(start_time, 'YYYY-MM-DD HH24:MI'), count(e.ts) AS events
FROM   generate_series(date_trunc('day', localtimestamp - interval '7 days')
                     , localtimestamp
                     , interval '15 min') g(start_time)
LEFT   JOIN event e ON e.ts >= g.start_time
                   AND e.ts <  g.start_time + interval '15 min'
GROUP  BY start_time
ORDER  BY start_time;

仍然在基础时间戳ORDER BY上,而不是在格式化字符串上。这样更快、更可靠。GROUP BY

db<>在这里摆弄

相关答案在时间范围内产生运行计数:

于 2013-03-22T18:28:31.327 回答