Гистограмма длительности сессий
Условие
Дана таблица sessions со следующей структурой:
- session_id (INT) — идентификатор сессии
- length_seconds (INT) — длительность сессии в секундах
Напишите SQL-запрос для подсчёта количества сессий в интервалах по 5 секунд с корректными метками (bucket labels).
Требования
- Интервалы: "0-5", "5-10", "10-15" и т.д.
- Вывести метку интервала и количество сессий
- Отсортировать по нижней границе интервала
Пример данных
| session_id | length_seconds |
|---|---|
| 1 | 3 |
| 2 | 7 |
| 3 | 12 |
| 4 | 4 |
| 5 | 8 |
Ожидаемый результат
| bucket | session_count |
|---|---|
| 0-5 | 2 |
| 5-10 | 2 |
| 10-15 | 1 |
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Задача требует создания гистограммы — распределения сессий по интервалам длительности. Это типичная задача для анализа пользовательского поведения и создания визуализаций.
Подход к решению
Логика:
- Определить нижнюю границу интервала для каждой сессии (floor to nearest 5)
- Создать метку интервала вида "X-Y"
- Сгруппировать по интервалам и подсчитать количество сессий
- Отсортировать по нижней границе
SQL-запрос (основной вариант)
WITH bucketed_sessions AS (
SELECT
session_id,
length_seconds,
(FLOOR(length_seconds / 5.0) * 5)::INT AS bucket_start,
(FLOOR(length_seconds / 5.0) * 5 + 5)::INT AS bucket_end
FROM sessions
)
SELECT
bucket_start || '-' || bucket_end AS bucket,
COUNT(*) AS session_count
FROM bucketed_sessions
GROUP BY bucket_start, bucket_end
ORDER BY bucket_start ASC;
Пошаговое объяснение
1. Расчёт границ интервала
FLOOR(length_seconds / 5.0)— определяет номер интервала (0, 1, 2, ...)FLOOR(...) * 5— нижняя граница интервала (0, 5, 10, ...)FLOOR(...) * 5 + 5— верхняя граница интервала (5, 10, 15, ...)::INT— преобразуем в целое число для читаемости
2. Метка интервала
bucket_start || '-' || bucket_end— конкатенация строк (0 || '-' || 5 = "0-5")
3. Группировка и подсчёт
GROUP BY bucket_start, bucket_end— группируем по интерваламCOUNT(*)— подсчитываем сессии в каждом интервале
4. Сортировка
ORDER BY bucket_start— выводим в порядке возрастания нижней границы
Вариант для MySQL
WITH bucketed_sessions AS (
SELECT
session_id,
length_seconds,
FLOOR(length_seconds / 5) * 5 AS bucket_start,
FLOOR(length_seconds / 5) * 5 + 5 AS bucket_end
FROM sessions
)
SELECT
CONCAT(bucket_start, '-', bucket_end) AS bucket,
COUNT(*) AS session_count
FROM bucketed_sessions
GROUP BY bucket_start, bucket_end
ORDER BY bucket_start ASC;
Вариант с использованием WIDTH_BUCKET (PostgreSQL)
Это более специализированная функция:
WITH bucketed_sessions AS (
SELECT
session_id,
length_seconds,
(WIDTH_BUCKET(length_seconds, 0, 100, 20) - 1) * 5 AS bucket_start
FROM sessions
)
SELECT
bucket_start || '-' || (bucket_start + 5) AS bucket,
COUNT(*) AS session_count
FROM bucketed_sessions
GROUP BY bucket_start
ORDER BY bucket_start ASC;
Альтернативный вариант: без CTE
SELECT
(FLOOR(length_seconds / 5) * 5)::TEXT || '-' ||
(FLOOR(length_seconds / 5) * 5 + 5)::TEXT AS bucket,
COUNT(*) AS session_count
FROM sessions
GROUP BY FLOOR(length_seconds / 5)
ORDER BY FLOOR(length_seconds / 5) ASC;
Расширенный вариант с дополнительной статистикой
WITH bucketed_sessions AS (
SELECT
session_id,
length_seconds,
FLOOR(length_seconds / 5.0) * 5 AS bucket_start
FROM sessions
)
SELECT
bucket_start || '-' || (bucket_start + 5) AS bucket,
COUNT(*) AS session_count,
COUNT(*)::NUMERIC / SUM(COUNT(*)) OVER () * 100 AS percentage,
MIN(length_seconds) AS min_length,
MAX(length_seconds) AS max_length,
ROUND(AVG(length_seconds), 2) AS avg_length
FROM bucketed_sessions
JOIN sessions USING (session_id)
GROUP BY bucket_start
ORDER BY bucket_start ASC;
Это добавляет проценты, минимум, максимум и среднее по каждому интервалу.
Пример выполнения
Исходные данные:
session_id | length_seconds
-----------|---------------
1 | 3
2 | 7
3 | 12
4 | 4
5 | 8
Расчёт для каждой сессии:
-
Сессия 1 (3 сек):
- FLOOR(3 / 5) = FLOOR(0.6) = 0
- bucket_start = 0 * 5 = 0
- bucket_end = 0 + 5 = 5
- bucket = "0-5"
-
Сессия 2 (7 сек):
- FLOOR(7 / 5) = FLOOR(1.4) = 1
- bucket_start = 1 * 5 = 5
- bucket_end = 5 + 5 = 10
- bucket = "5-10"
-
Сессия 3 (12 сек):
- FLOOR(12 / 5) = FLOOR(2.4) = 2
- bucket_start = 2 * 5 = 10
- bucket_end = 10 + 5 = 15
- bucket = "10-15"
-
Сессия 4 (4 сек): → "0-5"
-
Сессия 5 (8 сек): → "5-10"
Группировка:
bucket | session_count
--------|---------------
0-5 | 2 (сессии 1, 4)
5-10 | 2 (сессии 2, 5)
10-15 | 1 (сессия 3)
Для интервалов другого размера
Интервалы по 10 секунд:
SELECT
(FLOOR(length_seconds / 10.0) * 10)::TEXT || '-' ||
(FLOOR(length_seconds / 10.0) * 10 + 10)::TEXT AS bucket,
COUNT(*) AS session_count
FROM sessions
GROUP BY FLOOR(length_seconds / 10.0)
ORDER BY FLOOR(length_seconds / 10.0) ASC;
Интервалы по 1 минуте:
SELECT
(FLOOR(length_seconds / 60.0) * 60)::TEXT || '-' ||
(FLOOR(length_seconds / 60.0) * 60 + 60)::TEXT AS bucket,
COUNT(*) AS session_count
FROM sessions
GROUP BY FLOOR(length_seconds / 60.0)
ORDER BY FLOOR(length_seconds / 60.0) ASC;
Важные моменты
✓ FLOOR деление — критично для определения нижней границы интервала
✓ Целочисленное деление — использование 5.0 вместо 5 гарантирует правильное деление
✓ Граница интервала — сессия с 5 секундами попадёт в интервал "5-10" (FLOOR(5/5) = 1)
✓ Текстовая метка — конкатенация (||) для создания строки вида "0-5"
✓ Сортировка — важно сортировать по числовому значению (bucket_start), не по строке
✓ Динамические интервалы — если верхняя граница неизвестна, можно использовать MAX(length_seconds) + 5
✓ Гистограмма с нулями — если нужны интервалы без сессий, потребуется JOIN с генерируемой таблицей интервалов