Ответ
Для выделения сессий по таймауту неактивности я использую оконные функции, которые сравнивают время между последовательными событиями пользователя. Ключевая логика: новая сессия начинается, если разница с предыдущим событием превышает 5 минут (300 секунд) или это первое событие пользователя.
Детальное SQL-решение (на диалекте BigQuery, но принцип универсален):
WITH user_events AS (
-- Исходные данные: user_id, event_timestamp
SELECT
user_id,
TIMESTAMP '2024-08-30 10:00:00' AS event_timestamp UNION ALL
SELECT 'user1', TIMESTAMP '2024-08-30 10:01:00' UNION ALL
SELECT 'user1', TIMESTAMP '2024-08-30 10:07:00' UNION ALL -- Разница >5 мин, новая сессия
SELECT 'user2', TIMESTAMP '2024-08-30 10:00:00'
),
session_boundaries AS (
SELECT
user_id,
event_timestamp,
-- Флаг начала новой сессии: если перерыв >300 сек или это первая запись пользователя
CASE
WHEN TIMESTAMP_DIFF(
event_timestamp,
LAG(event_timestamp) OVER (PARTITION BY user_id ORDER BY event_timestamp),
SECOND
) > 300
THEN 1
WHEN LAG(event_timestamp) OVER (PARTITION BY user_id ORDER BY event_timestamp) IS NULL
THEN 1
ELSE 0
END AS is_new_session_flag
FROM user_events
),
session_groups AS (
SELECT
user_id,
event_timestamp,
-- Накопительная сумма флагов создает уникальный ID сессии для каждого пользователя
SUM(is_new_session_flag) OVER (PARTITION BY user_id ORDER BY event_timestamp) AS session_id
FROM session_boundaries
)
-- Финальная агрегация по сессиям
SELECT
user_id,
session_id,
MIN(event_timestamp) AS session_start,
MAX(event_timestamp) AS session_end,
TIMESTAMP_DIFF(MAX(event_timestamp), MIN(event_timestamp), SECOND) AS session_duration_sec,
COUNT(*) AS events_in_session
FROM session_groups
GROUP BY user_id, session_id
ORDER BY user_id, session_start;
| Результат для примера: | user_id | session_id | session_start | session_end | session_duration_sec | events_in_session |
|---|---|---|---|---|---|---|
| user1 | 1 | 2024-08-30 10:00:00 UTC | 2024-08-30 10:01:00 UTC | 60 | 2 | |
| user1 | 2 | 2024-08-30 10:07:00 UTC | 2024-08-30 10:07:00 UTC | 0 | 1 | |
| user2 | 1 | 2024-08-30 10:00:00 UTC | 2024-08-30 10:00:00 UTC | 0 | 1 |
Пояснение:
LAG()получает время предыдущего события для того же пользователя.TIMESTAMP_DIFF()вычисляет разницу в секундах.- Условие
> 300(5 минут) определяет таймаут. SUM(...) OVER (...)работает какcumsum()— накапливает флаги начала сессии, создавая уникальныйsession_idв рамках пользователя.