Группировка данных по временным интервалам — задача, с которой сталкивается каждый разработчик SQL. Будь то «заказы по 15-минуткам», «активность пользователей по часам» или «средний чек за 10 минут» — рано или поздно это нужно сделать.

Раньше, в версиях PostgreSQL до 14, это требовало изворотливости: date_trunc, extract, деление с округлением, снова умножение… А если нужно было сменить шаг с 15 минут на 10 — переписывать половину запроса.

Но начиная с PostgreSQL 14, у нас есть элегантное решение — функция date_bin.

Давайте разберёмся, как она работает и почему она лучше.


Как раньше делали группировку по интервалам

Предположим, у нас есть таблица заказов с полем created_at (тип timestamp). Нужно посчитать количество заказов за каждые 15 минут.

Пример запроса «до date_bin»:

sql
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 появилась функция, которая делает то же самое в одну строку.

Синтаксис:

sql
date_bin(stride, source, origin)

Где:

  • stride — шаг (интервал). Например: interval '15 minutes'.

  • source — исходная временная метка (timestamp).

  • origin — точка отсчёта, от которой начинаются интервалы.

Функция возвращает начало того интервала, в который попадает source.

📌 Простыми словами: date_bin отвечает на вопрос:

«В каком интервале длины N, если считать от точки O, лежит момент времени T?»


Переписываем пример с date_bin

Возьмём тот же набор данных и ту же задачу:

sql
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-минутным интервалам.

sql
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.

sql
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 — это современный, быстрый и правильный подход.


 

Статья подготовлена по материалам обучающего видео для вашего удобства.