Отто · операционный директор доставки «Гонец»
Академический мы открыли 15 апреля, а в отчёте по дням он начинается 22-го. А у Химмаша в мае просто пропали три дня. Это дыры в учёте или в вашем отчёте?
В прошлом уроке мы считали заказы по дням, часам и неделям через GROUP BY. У этого способа есть слепое пятно, и Отто на него наткнулся.
Журнал, в котором нет прогулов
Представь школьный журнал, где учитель отмечает только тех, кто пришёл. Хочешь узнать, когда Петя прогуливал, а в журнале про прогулы ни строчки. Чтобы увидеть прогулы, нужен список всех учебных дней, а уже рядом с ним отметки.
GROUP BY и есть такой журнал. Он группирует строки, которые есть. Дня без заказов в таблице orders нет, значит, нет и группы:
SELECT created_at::date AS day, count(*) AS orders
FROM orders
WHERE zone_id = 7 -- Академический
GROUP BY 1
ORDER BY 1;
day | orders
------------+--------
2026-04-22 | 1
2026-04-23 | 1
2026-04-24 | 1
2026-04-25 | 3
...
15 апреля зона открылась, но в отчёте неделя тишины исчезла. Нужен список всех дней, «учебный календарь».
generate_series: календарь из воздуха
generate_series возвращает не одно значение, а набор строк. Поэтому её ставят во FROM, как таблицу:
SELECT generate_series(1, 3) AS n;
n
---
1
2
3
С датами то же самое, только шаг это интервал:
SELECT d::date AS day
FROM generate_series(date '2026-04-15', date '2026-04-17', interval '1 day') AS d;
day
------------
2026-04-15
2026-04-16
2026-04-17
Без ::date строки придут как timestamptz с полуночью: функция считает дату плюс интервал моментом времени. Для дневного календаря приводим к date.
Дни без заказов
Теперь у нас есть журнал всех дней. Осталось найти те, для которых нет ни одного заказа. Это вопрос «существует ли», и мы уже умеем задавать его через NOT EXISTS:
SELECT d::date AS day
FROM generate_series(date '2026-04-15', date '2026-06-30', interval '1 day') AS d
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.zone_id = 7 AND o.created_at::date = d::date
)
ORDER BY 1;
day
------------
2026-04-15
2026-04-16
2026-04-17
2026-04-18
2026-04-19
2026-04-20
2026-04-21
2026-04-28
Восемь пустых дней: первая неделя после открытия целиком и 28 апреля. created_at записано по местным часам, поэтому created_at::date даёт местную дату, ровно то, что нужно Отто.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.