Postgres разделение строк на минуты интервалов

Вопрос или проблема

У меня есть таблица postgres с колонками временных меток:

start_datetime end_datetime duration id
2021-10-17 03:13:00 2021-10-17 03:15:02 302 6214550
2021-10-17 03:15:02 2021-10-17 04:17:03 4,021 6214551

Которую мне нужно разбить на временные интервалы, привязанные либо к концу минуты, либо к end_datetime следующим образом:

start_datetime end_datetime id
2021-10-17 03:13:00 2021-10-17 03:14:00 6214550
2021-10-17 03:14:00 2021-10-17 03:15:00 6214550
2021-10-17 03:15:00 2021-10-17 03:15:02 6214550
2021-10-17 03:15:02 2021-10-17 03:16:00 6214551
2021-10-17 03:16:00 2021-10-17 03:17:00 6214551
2021-10-17 03:17:00 2021-10-17 03:17:03 6214551

Схема (PostgreSQL v15)

CREATE TABLE t (
    "start_datetime" TIMESTAMP,
    "end_datetime" TIMESTAMP,
    "duration" INT,
    "id" INTEGER
);

INSERT INTO t
("start_datetime", "end_datetime", "duration", "id")
VALUES
('2021-10-17 03:13:00', '2021-10-17 03:15:02', '302', '6214550'),
('2021-10-17 03:15:02', '2021-10-17 04:17:03', '4021', '6214551');

Запрос #1

SELECT id
     -- Поскольку мы округлили время до минуты в generate_series, нам нужно обработать первую дату иначе,
     -- выбирая её вместо любых временных меток, предшествующих ей
     , CASE WHEN start_datetime > m THEN start_datetime ELSE m END AS start_datetime
     -- Мы находим следующую минуту (то есть end_datetime) с помощью LEAD(), кроме последней строки (по id), так как её не существует.
     -- Когда это так, мы используем end_datetime
     , COALESCE(LEAD(m) OVER(PARTITION BY id ORDER BY m), t.end_datetime) AS end_datetime
FROM t, generate_series(date_trunc('minute', start_datetime), end_datetime, '1 minute'::interval) AS f(m)
ORDER BY id, m;
id start_datetime end_datetime
6214550 2021-10-17T03:13:00.000Z 2021-10-17T03:14:00.000Z
6214550 2021-10-17T03:14:00.000Z 2021-10-17T03:15:00.000Z
6214550 2021-10-17T03:15:00.000Z 2021-10-17T03:15:02.000Z
6214551 2021-10-17T03:15:02.000Z 2021-10-17T03:16:00.000Z
6214551 2021-10-17T03:16:00.000Z 2021-10-17T03:17:00.000Z
урезано
6214551 2021-10-17T04:16:00.000Z 2021-10-17T04:17:00.000Z
6214551 2021-10-17T04:17:00.000Z 2021-10-17T04:17:03.000Z

Посмотреть на DB Fiddle

Кросс-соединение каждой строки с generate_series(), чтобы создать 1-минутные интервалы, используйте greatest() и least(), чтобы сохранить несоответствующие начальные и конечные временные метки.
демо на db<>fiddle

select greatest(slot,start_datetime)   as start_datetime
     , least(slot+'1min',end_datetime) as end_datetime
     , id
from test
cross join lateral generate_series( date_trunc('minute',start_datetime)
                                   ,end_datetime
                                   ,'1min') as slot;
start_datetime end_datetime id
2021-10-17 03:13:00 2021-10-17 03:14:00 6214550
2021-10-17 03:14:00 2021-10-17 03:15:00 6214550
2021-10-17 03:15:00 2021-10-17 03:15:02 6214550
2021-10-17 03:15:02 2021-10-17 03:16:00 6214551
2021-10-17 03:16:00 2021-10-17 03:17:00 6214551
2021-10-17 03:17:00 2021-10-17 03:17:03 6214551

Ответ или решение

Чтобы разделить строки таблицы PostgreSQL на интервалы в одну минуту на основе столбцов start_datetime и end_datetime, можно использовать функцию generate_series в комбинации с CROSS JOIN. Это позволит создать новые строки для каждого временного интервала в минуту.

Вот полное решение:

Шаг 1: Создание таблицы и вставка данных

Сначала создаем таблицу и заполняем её примерами данных:

CREATE TABLE t (
    start_datetime TIMESTAMP,
    end_datetime TIMESTAMP,
    duration INT,
    id INTEGER
);

INSERT INTO t
(start_datetime, end_datetime, duration, id)
VALUES
('2021-10-17 03:13:00', '2021-10-17 03:15:02', 302, 6214550),
('2021-10-17 03:15:02', '2021-10-17 04:17:03', 4021, 6214551);

Шаг 2: Создание запроса для разделения на интервалы

Теперь, используя generate_series, мы можем разделить временные промежутки на интервалы в одну минуту. Ключевым моментом будет использование функций GREATEST и LEAST, чтобы корректно обработать начало и конец каждого интервала.

SELECT 
    GREATEST(slot, start_datetime) AS start_datetime,
    LEAST(slot + interval '1 minute', end_datetime) AS end_datetime,
    id
FROM 
    t
CROSS JOIN LATERAL 
    generate_series(date_trunc('minute', start_datetime), end_datetime, '1 minute') AS slot
ORDER BY id, start_datetime;

Пояснение

  1. CROSS JOIN LATERAL: Используется для того, чтобы сгенерировать серию минут, начиная с округлённого вниз значения start_datetime и заканчив на end_datetime.

  2. GREATEST и LEAST:

    • GREATEST(slot, start_datetime): берёт максимальное значение между сгенерированным интервалом и фактическим временем начала.
    • LEAST(slot + interval '1 minute', end_datetime): выбирает меньшее значение из конца интервала в одну минуту и фактического end_datetime, что позволяет ограничивать временные промежутки по максимально допустимому времени.

Результат

Запрос вернёт несколько строк для каждой уникальной записи в таблице, которые будут в указанных временных интервалах по минутам. Например, для приведённых данных результат будет следующим:

start_datetime end_datetime id
2021-10-17 03:13:00 2021-10-17 03:14:00 6214550
2021-10-17 03:14:00 2021-10-17 03:15:00 6214550
2021-10-17 03:15:00 2021-10-17 03:15:02 6214550
2021-10-17 03:15:02 2021-10-17 03:16:00 6214551
2021-10-17 03:16:00 2021-10-17 03:17:00 6214551
2021-10-17 03:17:00 2021-10-17 03:17:03 6214551

Таким образом, используя предложенный запрос, вы сможете разделить временные промежутки на интервалы в одну минуту, учитывая как начало, так и конец указанного времени.

Оцените материал
Добавить комментарий

Капча загружается...