Условие
Таблица sessions(user_id, login_time, logout_time) — сессии онлайна. Найдите час суток, в который было максимальное число одновременно онлайн-пользователей.
Решение
Подход 1 — sweep line по событиям
Идея: каждый login = +1, каждый logout = -1. Сортируем по времени и считаем накопительную сумму.
WITH events AS (
SELECT login_time AS ts, 1 AS delta FROM sessions
UNION ALL
SELECT logout_time AS ts, -1 AS delta FROM sessions
),
running AS (
SELECT ts, SUM(delta) OVER (ORDER BY ts, delta DESC) AS online_count
FROM events
)
SELECT EXTRACT(HOUR FROM ts) AS hour, MAX(online_count) AS peak
FROM running
GROUP BY EXTRACT(HOUR FROM ts)
ORDER BY peak DESC
LIMIT 1;Деталь: ORDER BY ts, delta DESC — на одно и то же время сначала обрабатываем login (+1), потом logout (-1). Это договорённость; при ts_login = ts_logout чёткой картины «одновременности» нет.
Подход 2 — calendar table часов и CROSS JOIN
WITH hours AS (
SELECT generate_series(0, 23) AS h
)
SELECT h.h AS hour, COUNT(*) AS online_in_hour
FROM hours h
JOIN sessions s
ON EXTRACT(HOUR FROM s.login_time) <= h.h
AND EXTRACT(HOUR FROM s.logout_time) >= h.h
GROUP BY h.h
ORDER BY online_in_hour DESC
LIMIT 1;Этот вариант наивен: считает «сессию, частично попавшую в час», а не «одновременно онлайн в любой момент часа». Для интервью обычно ждут sweep-line.
Подводные камни
UNIONбез ALL. Удалит «равные» события (две логина в одно мгновение) — задвоится подсчёт. ТолькоUNION ALL.- Logout = NULL. Если пользователь ещё онлайн —
logout_time IS NULL. Заменить наCURRENT_TIMESTAMP. Иначе строка просто пропадёт из UNION. EXTRACT(HOUR ...)теряет дату. Если просят «час за все дни» — норм. Если просят «час конкретного дня» — нужноDATE_TRUNC('hour', ...).- Тай-брейк часов. Если у двух часов одинаковый пик —
LIMIT 1даст случайный.RANK() = 1или возврат всех.
Эталонный ответ
Sweep-line через UNION ALL login/logout с весами +1/-1, SUM OVER (ORDER BY ts) для running count, MAX по часу. Очень популярный паттерн на собесах в FAANG.