Как найти пропуски в датах SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это знать
Пропуски в данных = пропуски в отчёте: отсутствующие дни «съедают» seasonality, dashboards показывают неверную динамику. Частая задача в ETL и ad-hoc: «вот daily events, найди дни без событий».
На собесах задача «найти дни, когда не было заказов» — классика middle-уровня. Решается 3 способами, и умение выбрать правильный — важно.
Способ 1: generate_series (Postgres)
SELECT d.day FROM (
SELECT generate_series('2026-01-01'::DATE, '2026-04-30'::DATE, '1 day') AS day
) d
LEFT JOIN orders o ON DATE(o.created_at) = d.day
WHERE o.id IS NULL;Полный calendar → LEFT JOIN → NULLs = missing days.
Способ 2: calendar table
Если часто нужно — завести справочную таблицу calendar:
SELECT c.day FROM calendar c
LEFT JOIN orders o ON DATE(o.created_at) = c.day
WHERE o.id IS NULL AND c.day BETWEEN '2026-01-01' AND '2026-04-30';Работает везде, не только Postgres.
Способ 3: LAG для gap detection
Найти пропуски между существующими датами:
WITH days AS (
SELECT DISTINCT DATE(created_at) AS day FROM orders
),
gaps AS (
SELECT day,
LAG(day) OVER (ORDER BY day) AS prev_day,
day - LAG(day) OVER (ORDER BY day) AS diff
FROM days
)
SELECT * FROM gaps WHERE diff > 1;Покажет даты, перед которыми был gap > 1 дня.
Найти непрерывные streaks
Обратная задача — найти последовательности:
WITH numbered AS (
SELECT day, day - ROW_NUMBER() OVER (ORDER BY day) AS grp
FROM days
)
SELECT MIN(day), MAX(day), COUNT(*) AS streak_len
FROM numbered
GROUP BY grp;Классический pattern «gaps and islands».
С hourly данными
Для часовых пропусков:
SELECT generate_series(
'2026-04-22 00:00'::TIMESTAMP,
'2026-04-22 23:00'::TIMESTAMP,
'1 hour'
) AS hour;Потом LEFT JOIN.
На собесе
Вопрос: «Есть таблица orders с created_at. Найдите дни без заказов за последний месяц».
Идеальный ответ: generate_series (или calendar) → LEFT JOIN → WHERE NULL.
Если скажут «нет generate_series» — предложите CTE с рекурсией или calendar table.
Рекурсивный CTE (если нет generate_series)
WITH RECURSIVE dates AS (
SELECT '2026-01-01'::DATE AS d
UNION ALL
SELECT d + 1 FROM dates WHERE d < '2026-04-30'
)
SELECT d.d FROM dates d
LEFT JOIN orders o ON DATE(o.created_at) = d.d
WHERE o.id IS NULL;Работает в Postgres, MSSQL, SQLite.
Связанные темы
FAQ
Какой способ быстрее?
Calendar table — быстрее, если уже есть. Generate_series — удобнее на лету.
Gaps and islands — что это?
Pattern: найти «острова» непрерывных значений, разделённые «пропусками».
Работает в MySQL?
Generate_series нет, используйте calendar table или numbers table.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.