我记录了带有开始和结束时间戳的会话持续时间:
user_id | session_id | session_start | session_end
--------+------------+-------------------------------+------------------------------
1 | 1 | 2021-02-25 10:10:00.000 +0100 | 2021-02-25 10:20:00.000 +0100
1 | 2 | 2021-02-25 10:50:00.000 +0100 | 2021-02-25 10:55:00.000 +0100
1 | 3 | 2021-02-25 11:40:00.000 +0100 | 2021-02-25 12:30:00.000 +0100
获取每个会话的持续时间就像减去两个时间戳一样简单。现在,我想用挂钟每小时桶来表示会话持续时间,每个用户求和。
这里的主要问题是间隔跨越多个小时的会话。一个从 11:40 开始到 12:30 结束的会话应该用 11:00 20 分钟的存储桶和 12:00 30 分钟的存储桶来表示:
user_id | bucket | duration
--------+----------+---------
1 | 00:00:00 | 00:00:00
1 | 01:00:00 | 00:00:00
...
1 | 10:00:00 | 00:15:00
1 | 11:00:00 | 00:20:00
1 | 12:00:00 | 00:30:00
我尝试使用time_series
and date_trunc
,但没有成功。
理想情况下,存储桶还包括日期,这也可能简化逻辑。如果没有,一次选择一天也可以。
user_id | bucket | duration
--------+---------------------+----------
1 | 2021-02-25 00:00:00 | 00:00:00
1 | 2021-02-25 01:00:00 | 00:00:00
...
1 | 2021-02-25 10:00:00 | 00:15:00
1 | 2021-02-25 11:00:00 | 00:20:00
1 | 2021-02-25 12:00:00 | 00:30:00
我将使用查询结果生成一个热图,其中一个轴为用户,另一个轴为小时。
生成一系列时间戳范围,加入您的数据,计算重叠并聚合:
你可以做这样的事情——下面的所有代码都可以在这里找到。该解决方案利用了 PostgreSQL范围类型——一个非常强大的工具,尤其是对于这类工作。我还使用了一个物化日历表,按照此处的说明生成。
然后填充它:
我添加了最后一条记录进行测试 - 时隙的边界与日历表的 10 分钟不匹配。
现在,您必须有一个日历表 - 这是为了
JOIN
您的时间段并执行计算。现在,每条记录需要 50 个字节,因此对于 100MB,您将拥有超过 30 年的记录 - 或者您可能希望按照其他答案动态生成它 - 您的存储、CPU 和 RAM 会告诉您该怎么做在这里-我建议永久日历表是更好的解决方案,特别是如果您定期进行此类计算!它也将更加高效。
我在小提琴上留下了一些我的第一个查询 - 这是最后一个:
只是为了检查我们的
calendar_range
桌子:结果:
因此,我们有一个范围(包括开始,不包括结束边界),从 2021 年开始并持续 100 天 - 足以涵盖问题中的样本数据。
然后我们对实际时隙数据做一个
SELECT
和JOIN
日历表如下:注意 &&
OVERLAPS
运算符的使用。结果:
插槽的持续时间为 10 分钟(根据问题 - 对于 1 小时长的插槽,请在此处查看小提琴)。
hour_slot
如果您有兴趣了解您的事件发生在一天中的哪个小时,我还包括了一个字段。当然,您可以根据自己的要求进行更改-原理相同-您可以根据需要SUM()
和GROUP BY
各种插槽。请注意,从 11:46 到 11:57 的时段已正确计算 - 从
11:40
-开始的时段为 4 分钟,在-时段为11:50
7 分钟。请务必检查边缘情况,因为它们很容易被遗漏。11:50
12:00
请确保您了解包含/排除边界符号(开始和结束方 (
[]
) 和圆 (()
))括号 - 以及如何使用它们 - 任何混淆都可能是微妙的、难以发现的错误的根源!最后,您似乎将
TIMESTAMP
s 与TIME ZONE
- 即 theTIMESTAMPTZ
和它们相应的范围类型一起使用 - 这是一件好事!您应该始终将 UTC 用于与时间戳有关的任何事情 - 而不是存储例如偏移量 - 因为这可能会根据 DST(夏令时)而有所不同。以下评论:
How could I get rows for all ranges - also where there is no corresponding tab entry (where duration is zero, where user_id is null)?
结果:
注意会话 2 计算的 5 分钟。此外,使用该
WHERE
子句限制范围 - 以免有太多空记录。我在这里使用了图像而不是发布文本,因为对于所有NULL
s,对齐记录非常困难。Ah, I think my examples gave an impression that I'm looking for 10-minute slots, sorry for that! I was only looking for 1-hour slots
每小时时段的原理是完全一样的——请看这里的小提琴。