在Rails + Postgres中按任意时间间隔计算记录的最佳方法

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

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

例如,用户可以显示他们每周,每天,每小时或每季度收到的订单数量。

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

2)一种简单但丑陋的暴力方法是对按时间戳排序的开始/结束时间帧内的所有记录执行单个查询,然后使用方法手动按任意间隔构建计数。

3)另一种方法是为每个间隔向事件表添加单独的字段,并静态存储the_week the_daythe_hourthe_quarter_hour字段,这样我就可以在创建记录时(一次)取“命中”而不是每次我关于该领域的报告。

这里有什么最好的做法,因为我可以根据需要修改模型并预先存储间隔数据(尽管只需要将表宽增加一倍)?

幸运的是,你正在使用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

  • 确保正确处理上限和下限

    • 使用BETWEEN时间戳的SQL查询的意外结果
  • 窗口函数lead()有一个经常被忽略的特性:它可以在没有前导行时提供默认值。 在示例中提供'infinity' 。 否则,最后一个间隔将被上限为NULL

最小的等价物

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

 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 BYGROUP BY ,而不是格式化的字符串。 这更快,更可靠。

db <> 在这里小提琴

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

  • PostgreSQL:按分钟运行查询的行数
Interesting Posts