Получить количество подходящих диапазонов времени для каждой минуты дня в Postgres - PullRequest
0 голосов
/ 15 января 2020

Проблема

У меня есть таблица записей, каждая из которых содержит id, in_datetime и out_datetime. Запись считается «открытой» в течение времени между in_datetime и out_datetime. Я хочу знать, сколько записей времени было «открыто» для каждой минуты дня (независимо от даты). Например, за последние 90 дней я хочу знать, сколько записей было «открыто» в 3:14, затем в 3:15, затем в 3:16, затем ... Если в 2 не было «открытых» записей : 00 утра запрос должен возвращать 0 или ноль вместо исключения строки, поэтому всегда должно быть возвращено 1440 строк (количество минут в дне). Дата и время хранятся в UT C и должны быть приведены к часовому поясу.

Упрощенный пример графика c

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.) В подзапросе я в настоящее время генерирует мелкие серии времен для всего диапазона каждой временной записи. Затем я группирую их по времени и получаю количество записей в минуту. Вот db-скрипка, использующая мой текущий запрос:

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.) Изменяя запрос Гордона Линоффа в его ответе ниже, я пришел к этому ( 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 Ответ

0 голосов
/ 15 января 2020

Вот более быстрый метод. Создайте записи «in» и «out», когда что-то подсчитывается. Затем агрегируйте и используйте текущую сумму.

Чтобы получить все минуты, добавьте 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;
...