幸运的是,您使用的是 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<>在这里摆弄
相关答案在时间范围内产生运行计数: