Условие
Дана таблица events(user_id, event, timest). Нужно добавить колонку session_id: если между двумя действиями пользователя проходит более 30 минут, они принадлежат разным сессиям. Иначе — одной.
Решение
Подход — gaps and islands
- Для каждой строки сравниваем
timestс предыдущимtimestтого же пользователя (LAG). - Если разница > 30 мин — это start новой сессии (флаг 1).
- Накопительная сумма флагов =
session_id.
SQL
WITH lagged AS (
SELECT
user_id, event, timest,
LAG(timest) OVER (PARTITION BY user_id ORDER BY timest) AS prev_t
FROM events
),
breaks AS (
SELECT
user_id, event, timest,
CASE
WHEN prev_t IS NULL THEN 1
WHEN EXTRACT(EPOCH FROM (timest - prev_t)) > 1800 THEN 1
ELSE 0
END AS is_new_session
FROM lagged
),
sessions AS (
SELECT
user_id, event, timest,
SUM(is_new_session) OVER (
PARTITION BY user_id ORDER BY timest
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS session_seq
FROM breaks
)
SELECT
user_id, event, timest,
user_id || '_' || session_seq AS session_id
FROM sessions
ORDER BY user_id, timest;Альтернатива (Hive/SparkSQL)
Без EXTRACT(EPOCH ...) можно использовать unix_timestamp(timest) - unix_timestamp(prev_t) > 1800.
Подводные камни
- 30 минут включительно или строго: обычно «более 30 мин» =
> 1800 сек. В формулировке≥ 30 минмогут хотеть>=. session_idуникален в пределах юзера — добавилuser_id || '_' ||для глобальной уникальности.- Если у пользователя только одно событие — у него тоже сессия (
is_new_session = 1). LAGбез partition даст «глобальное прошлое», что неверно — обязательноPARTITION BY user_id.- Производительность: индекс по
(user_id, timest)обязателен.
Эталонный ответ
Cum sum по флагу «разрыв > 30 мин» (gaps and islands): LAG-разница + SUM() OVER = порядковый номер сессии в пределах пользователя.