在 Postgres 中获取一天中每一分钟的匹配时间范围计数
Posted
技术标签:
【中文标题】在 Postgres 中获取一天中每一分钟的匹配时间范围计数【英文标题】:Get count of matching time ranges for every minute of the day in Postgres 【发布时间】:2020-01-15 19:18:23 【问题描述】:问题
我有一个记录表,每个记录包含id
、in_datetime
和out_datetime
。在in_datetime
和out_datetime
之间的时间段内,记录被视为“打开”。我想知道一天中每一分钟有多少时间记录“打开”(无论日期如何)。例如,对于过去 90 天,我想知道在凌晨 3 点 14 分、凌晨 3 点 15 分、凌晨 3 点 16 分、然后……如果没有记录在凌晨 2 点“打开” :00 am 查询应返回 0 或 null 而不是排除该行,因此应始终返回 1440 行(一天中的分钟数)。日期时间以 UTC 格式存储,需要转换为时区。
简化示例图形
record_id | time_range
| 0123456789 (these are minutes past midnight)
1 | =========
2 | ===
3 | =======
4 | ===
5 | ==
______________________
result 3323343210
期望的输出
time | count of open records at this time
00:00 120
00:01 135
00:02 132
...
23:57 57
23:58 62
23:59 60
由于一天只有 1440 分钟,因此不会返回超过 1440 条记录。
我尝试过的
1.) 在子查询中,我目前为每个时间记录的整个范围生成了一系列时间。然后我按时间对它们进行分组并计算每分钟的记录数。 Here is a db-fiddle using my current query:
select
trs.minutes,
count(trs.minutes)
from (
select
generate_series(
DATE_TRUNC('minute', (time_records.in_datetime::timestamptz AT TIME ZONE 'America/Denver')),
DATE_TRUNC('minute', (time_records.out_datetime::timestamptz AT TIME ZONE 'America/Denver')),
interval '1 min'
)::time as minutes
from
time_records
) trs
group by
trs.minutes
这可行,但效率很低,由于我的桌子很大,需要几秒钟才能运行。此外,它不包括没有打开记录的时间。我想我可以以某种方式使用窗口函数来计算一天中每一分钟的重叠时间记录的数量,但我不太明白该怎么做。
2.) 在下面的回答中修改 Gordon Linoff 的查询,我来到了这个 (db-fiddle link):
with tr as (
select
date_trunc('minute', (tr.in_datetime::timestamptz AT TIME ZONE 'America/Denver'))::time as m,
1 as inc
from
time_records tr
union all
select
(date_trunc('minute', (tr.out_datetime::timestamptz AT TIME ZONE 'America/Denver')) + interval '1 minute')::time as m,
-1 as inc
from
time_records tr
union all
select
minutes::time,
0
from
generate_series(timestamp '2000-01-01 00:00', timestamp '2000-01-01 23:59', interval '1 min') as minutes
)
select
m,
sum(inc) as changes_at_inc,
sum(sum(inc)) over (order by m) as running_count
from
tr
where
m is not null
group by
m
order by
m;
这运行得相当快,但在一天结束时(在链接的示例中大约是 22:00 开始),由于某种原因,值变为负数。此外,此查询似乎不适用于时间范围跨越午夜的记录。这是朝着正确方向迈出的一步,但不幸的是,我对它的理解还不够深入,无法进一步改进。
【问题讨论】:
【参考方案1】:这是一种更快的方法。生成“输入”和“输出”记录,以便计算某些内容。然后聚合并使用运行总和。
要获得所有分钟数,请在相关时间段内输入generate_series()
:
with tr as (
select date_trunc('minute', (tr.in_datetime::timestamptz AT TIME ZONE 'America/Denver')) as m,
1 as inc
from time_records tr
union all
select date_trunc('minute', (tr.out_datetime::timestamptz AT TIME ZONE 'America/Denver')) + interval '1 minute' as m,
-1 as inc
from time_records tr
union all
select generate_series(date_trunc('minute',
min(tr.in_datetime::timestamptz AT TIME ZONE 'America/Denver')),
date_trunc('minute',
max(tr.out_datetime::timestamptz AT TIME ZONE 'America/Denver')),
interval '1 minute'
), 0
from time_records tr
)
select m,
sum(inc) as changes_at_inc,
sum(sum(inc)) over (order by m) as running_count
from tr
group by m
order by m;
【讨论】:
我必须删除一个额外的)
并添加 interval '1 min'
才能运行它,但我不确定我是否理解这里发生了什么?它返回的行数超过 1440 行,并且比我的原始查询花费的时间更长。
进一步修改您的查询,我能够得到接近我正在寻找的东西:db-fiddle.com/f/n1ibdtf5Ri6oAsiFyYQnaK/1 但在一天结束时(大约 22:00 开始),值变为负数出于某种原因。以上是关于在 Postgres 中获取一天中每一分钟的匹配时间范围计数的主要内容,如果未能解决你的问题,请参考以下文章