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

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

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

1) 我的首选是动态执行单个SQL查询(我使用的是Postgres),按任意时间间隔分组.有办法吗?

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

3) 另一种方法是 for each 时间间隔向事件表中添加单独的字段,并静态存储一个the_week the_daythe_hourthe_quarter_hour字段,以便我在创建记录时(一次)获取"命中",而不是每次报告该字段时.

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

推荐答案

幸运的是,您正在使用PostgreSQL.窗口功能generate_series()是您的朋友.

测试用例

给出以下测试表(you应提供):

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';

One event for every 7 minutes (plus 0 to 7 minutes, randomly).

碱性溶液

此查询统计任意时间间隔内的事件.在本例中为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,以覆盖整个时间范围.您可以使用任意的时间范围.

根据需要提供any time interval个.

every个时隙生成一行.如果在这段时间内没有发生任何事件,则计数为0.

确保正确处理upper and lower bound.见:

窗口函数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;

Example for "every 15 minutes in the past week"`

格式为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;

在底层时间戳value上仍然是ORDER BYGROUP BY,而不是在格式化字符串上.这样更快更可靠.

db<>fiddle 100

相关答案在时间范围内得出running count分:

Postgresql相关问答推荐

Redis作为postgreSQL嵌套数据的缓存

优化PostgreSQL查询以将用户插入数据库

分区可以用于postgres中的同类查询吗?

Postgresql我必须创建一个索引还是已经有一个索引了?

如何在PostgreSQL中更改分区的表空间?

为什么 Postgres 中的 now() 和 now()::timestamp 对于 CET 和 CEST 时区如此错误?

在 jOOQ 中使用 $$ 引用字符串

如何计算每月的出版物数量?

PostgreSQL pg_dump 创建 sql 脚本,但它不是 sql 脚本:有没有办法让 pg_dump 创建标准的 sql 脚本?

使用 select 在带有特殊字符的字符串中查找数据

PostgreSQL比较两个jsonb对象

推送到 Heroku 时出现带有 Postgres 的 Rails 迁移错误

在 to_tsquery 中转义特殊字符

如何使用 SpringBoot + JPA 存储 PostgreSQL jsonb?

如何防止materialized 视图在 pg_restore 期间刷新?

Rails 4 迁移: has_and_belongs_to_many table name

从 PostgreSQL 中的时间戳获取日期

无法使用 sequelize 从本地 node 应用程序连接到 heroku postgresql 数据库

当成功有时会导致退出代码为 1 时,如何可靠地确定 pg_restore 是否成功?

如何使用 PostgreSQL 在任何列中查找所有具有 NULL 值的行