我的应用程序有一个带有时间戳事件的Events
表。
我需要在最近N
个时间间隔内报告事件数。对于不同的报告,间隔可以是“每周”或“每天”或“每小时”或“每15分钟间隔”。
例如,用户可以显示他们每周,每天,每小时或每季度收到的订单数量。
1)我的偏好是动态地执行单个SQL查询(我正在使用Postgres)按任意时间间隔进行分组。有没有办法做到这一点?
2)一种简单而又丑陋的暴力方法是对按时间戳排序的开始/结束时间范围内的所有记录执行单个查询,然后让方法按任意间隔手动构建计数。
3)另一种方法是为每个间隔向事件表添加单独的字段,并静态存储the_week
the_day
,the_hour
和the_quarter_hour
字段,以便我采取创建记录时的“点击”(一次),而不是每次我报告该字段时。
这里有什么最好的做法,因为我可以根据需要修改模型并预先存储间隔数据(尽管只需要将表宽增加一倍)?
答案 0 :(得分:37)
幸运的是,你正在使用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
切断。
上述查询使用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;
使用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&lt;&gt;小提琴here
相关答案在时间范围内产生运行计数: