Группировка данных по временным интервалам — задача, с которой сталкивается каждый разработчик SQL. Будь то «заказы по 15-минуткам», «активность пользователей по часам» или «средний чек за 10 минут» — рано или поздно это нужно сделать.
Раньше, в версиях PostgreSQL до 14, это требовало изворотливости: date_trunc, extract, деление с округлением, снова умножение… А если нужно было сменить шаг с 15 минут на 10 — переписывать половину запроса.
Но начиная с PostgreSQL 14, у нас есть элегантное решение — функция date_bin.
Давайте разберёмся, как она работает и почему она лучше.
Как раньше делали группировку по интервалам
Предположим, у нас есть таблица заказов с полем created_at (тип timestamp). Нужно посчитать количество заказов за каждые 15 минут.
Пример запроса «до date_bin»:
WITH orders AS ( SELECT generate_series( '2024-01-01 00:00:00', '2024-01-01 23:59:59', interval '1 minute' ) AS created_at LIMIT 1000 -- генерируем 1000 случайных заказов ) SELECT date_trunc('hour', created_at) + (extract(minute FROM created_at)::int / 15) * interval '15 minutes' AS bin_start, count(*) FROM orders GROUP BY bin_start ORDER BY bin_start;
Проблемы такого подхода:
-
Код сложно читать.
-
Вычисления происходят для каждой строки.
-
Индексы по полю
created_atиспользуются неэффективно. -
Смена шага (например, на 10 минут) требует правки нескольких мест.
Знакомьтесь: date_bin
В PostgreSQL 14 появилась функция, которая делает то же самое в одну строку.
Синтаксис:
date_bin(stride, source, origin)
Где:
-
stride— шаг (интервал). Например:interval '15 minutes'. -
source— исходная временная метка (timestamp). -
origin— точка отсчёта, от которой начинаются интервалы.
Функция возвращает начало того интервала, в который попадает source.
📌 Простыми словами: date_bin отвечает на вопрос:
«В каком интервале длины N, если считать от точки O, лежит момент времени T?»
Переписываем пример с date_bin
Возьмём тот же набор данных и ту же задачу:
WITH orders AS ( SELECT generate_series( '2024-01-01 00:00:00', '2024-01-01 23:59:59', interval '1 minute' ) AS created_at LIMIT 1000 ) SELECT date_bin(interval '15 minutes', created_at, '2024-01-01 00:00:00') AS bin_start, count(*) FROM orders GROUP BY bin_start ORDER BY bin_start;
✅ Код стал короче, понятнее и быстрее.
Наглядный пример работы
Давайте посмотрим, как date_bin округляет события по 15-минутным интервалам.
WITH events(time) AS (VALUES ('09:03'::time), ('09:07'::time), ('09:12'::time), ('09:19'::time), ('09:27'::time), ('09:33'::time) ) SELECT time, date_bin(interval '15 minutes', time, '09:00'::time) AS bin_start FROM events;
Результат:
| time | bin_start |
|---|---|
| 09:03 | 09:00 |
| 09:07 | 09:00 |
| 09:12 | 09:00 |
| 09:19 | 09:15 |
| 09:27 | 09:15 |
| 09:33 | 09:30 |
Видно, что все события «корректно» попадают в свои 15-минутные корзины.
Важный нюанс: параметр origin
Параметр origin становится критически важным, когда вы работаете не с «полуночным» отсчётом, а с бизнес-часами.
Пример: ваш рабочий день начинается в 9:00. Если в качестве origin указать '2024-01-01 09:00:00', интервалы будут строиться именно от этого момента.
Без этого группировка может «съехать» относительно реальных бизнес-процессов.
Бонус: строим гистограмму прямо в консоли
С помощью date_bin можно быстро построить примитивную гистограмму без выгрузки в Excel.
WITH minutes AS ( SELECT generate_series('09:00'::timestamp, '10:00'::timestamp, interval '1 minute') AS t ), events AS ( SELECT t FROM minutes WHERE random() < 0.3 -- примерно треть событий ) SELECT date_bin(interval '15 minutes', t, '09:00') AS bin_start, count(*) AS cnt, repeat('#', count(*)) AS bar FROM events GROUP BY bin_start ORDER BY bin_start;
Пример вывода:
| bin_start | cnt | bar |
|---|---|---|
| 09:00 | 5 | ##### |
| 09:15 | 7 | ####### |
| 09:30 | 3 | ### |
| 09:45 | 6 | ###### |
Очень удобно для быстрого анализа на месте.
Итог
date_bin — это:
-
✅ Читаемый код
-
✅ Простота смены шага
-
✅ Поддержка индексов
-
✅ Контроль начала интервалов через
origin
Если вы работаете с временными рядами в PostgreSQL, используйте date_bin — это современный, быстрый и правильный подход.
Статья подготовлена по материалам обучающего видео для вашего удобства.