← Назад к вопросам
SQL: Определение непрерывных периодов активности
2.0 Middle🔥 161 комментариев
#SQL и базы данных#Аналитика и метрики
Условие
Дана таблица с датами активности пользователей:
CREATE TABLE user_activity (
user_id INT,
activity_date DATE
);
Пример данных:
| user_id | activity_date |
|---|---|
| 1 | 2024-01-01 |
| 1 | 2024-01-02 |
| 1 | 2024-01-03 |
| 1 | 2024-01-05 |
| 1 | 2024-01-06 |
| 2 | 2024-01-01 |
| 2 | 2024-01-03 |
Задание:
- Найдите непрерывные периоды активности для каждого пользователя
- Рассчитайте длительность каждого периода
- Найдите самый длинный период активности для каждого пользователя
Ожидаемый результат для user_id = 1:
| user_id | period_start | period_end | duration |
|---|---|---|---|
| 1 | 2024-01-01 | 2024-01-03 | 3 |
| 1 | 2024-01-05 | 2024-01-06 | 2 |
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Задача и подход
Нужно обнаружить непрерывные периоды активности (консецутивные дни) для каждого пользователя. Используем window functions для вычисления разницы между текущей датой и рангом, чтобы идентифицировать разрывы.
Основной алгоритм:
- Сортируем активности по дате
- Вычисляем ранг каждой даты
- Вычитаем ранг из даты — если значение одинаковое для соседних строк, они в одном периоде
- Группируем по найденному периоду
Шаг 1: Определение непрерывных периодов
WITH activity_with_period AS (
SELECT
user_id,
activity_date,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY activity_date) AS rn,
DATE_SUB(activity_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY activity_date) DAY) AS period_key
FROM user_activity
)
SELECT
user_id,
MIN(activity_date) AS period_start,
MAX(activity_date) AS period_end,
DATE_DIFF(MAX(activity_date), MIN(activity_date), DAY) + 1 AS duration_days
FROM activity_with_period
GROUP BY user_id, period_key
ORDER BY user_id, period_start;
Объяснение:
- ROW_NUMBER() присваивает порядковый номер каждой дате внутри user_id
- Вычитая день из даты, получаем "якорь" периода (период_key одинаков для консецутивных дат)
- GROUP BY период_key группирует непрерывные дни
Шаг 2: Самый длинный период для каждого пользователя
WITH activity_with_period AS (
SELECT
user_id,
activity_date,
DATE_SUB(activity_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY activity_date) DAY) AS period_key
FROM user_activity
),
period_duration AS (
SELECT
user_id,
period_key,
MIN(activity_date) AS period_start,
MAX(activity_date) AS period_end,
DATE_DIFF(MAX(activity_date), MIN(activity_date), DAY) + 1 AS duration_days
FROM activity_with_period
GROUP BY user_id, period_key
),
ranked_periods AS (
SELECT
user_id,
period_start,
period_end,
duration_days,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY duration_days DESC) AS rank
FROM period_duration
)
SELECT
user_id,
period_start,
period_end,
duration_days
FROM ranked_periods
WHERE rank = 1;
Альтернативный подход для PostgreSQL
WITH activity_with_period AS (
SELECT
user_id,
activity_date,
activity_date - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY activity_date) * INTERVAL '1 day' AS period_key
FROM user_activity
)
SELECT
user_id,
MIN(activity_date) AS period_start,
MAX(activity_date) AS period_end,
COUNT(*) AS duration_days
FROM activity_with_period
GROUP BY user_id, period_key
ORDER BY user_id, period_start;
Результаты для примера
Все периоды:
- User 1, 2024-01-01 to 2024-01-03: 3 дня
- User 1, 2024-01-05 to 2024-01-06: 2 дня
- User 2, 2024-01-01: 1 день
- User 2, 2024-01-03: 1 день
Самый длинный:
- User 1: 2024-01-01 to 2024-01-03 (3 дня)
- User 2: 2024-01-01 (1 день)