Percentile в SQL: шпаргалка
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Что такое перцентили
P-perсентиль — значение, ниже которого находится P% данных.
- P50 = медиана (половина выше, половина ниже)
- P25, P75 = квартили
- P95, P99 = часто используют для анализа «долгих» хвостов (время отклика)
Основные функции
| Функция | Что делает |
|---|---|
PERCENTILE_CONT(p) |
Интерполированное значение |
PERCENTILE_DISC(p) |
Ближайшее значение из данных |
NTILE(n) |
Разбивает на n одинаковых групп |
AVG / MIN / MAX |
Для контекста |
1. Медиана (PERCENTILE_CONT)
Postgres
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total) AS median
FROM orders;MySQL 8+ / BigQuery / Snowflake
-- Snowflake / Oracle
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total) AS median
FROM orders;
-- BigQuery
SELECT PERCENTILE_CONT(total, 0.5) OVER () AS median
FROM orders;
-- MySQL 8+ (нестандартно, но работает)
SELECT total AS median
FROM (
SELECT total, ROW_NUMBER() OVER (ORDER BY total) AS rn, COUNT(*) OVER () AS cnt
FROM orders
) t
WHERE rn = CEIL(cnt / 2);2. Квартили (P25, P50, P75)
SELECT
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total) AS p25,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY total) AS p50,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY total) AS p75
FROM orders;3. P95, P99 (для response time / longtail)
SELECT
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY response_time_ms) AS p95,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_time_ms) AS p99,
PERCENTILE_CONT(0.999) WITHIN GROUP (ORDER BY response_time_ms) AS p999
FROM api_logs
WHERE created_at >= NOW() - INTERVAL '1 hour';4. Перцентили по группам
SELECT
category,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total) AS median,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY total) AS p95
FROM orders
GROUP BY category;5. PERCENTILE_CONT vs PERCENTILE_DISC
Разница:
PERCENTILE_CONT(0.5)— интерполирует между значениямиPERCENTILE_DISC(0.5)— возвращает ближайшее реальное значение
-- для [10, 20, 30, 40]:
PERCENTILE_CONT(0.5) -- 25 (интерполяция)
PERCENTILE_DISC(0.5) -- 20 (ближайшее реальное)Для нечётного числа — оба возвращают то же значение.
Обычно хотят PERCENTILE_CONT для среднего позиционного значения.
6. NTILE — разбить на N групп
-- квартили: 4 группы по 25%
SELECT
user_id,
total,
NTILE(4) OVER (ORDER BY total) AS quartile
FROM orders;
-- децили: 10 групп по 10%
SELECT
user_id,
total,
NTILE(10) OVER (ORDER BY total) AS decile
FROM orders;Используется для сегментации: «Топ-20% самых платящих» = NTILE(5) = 1.
7. Размер каждого перцентиля
-- сколько заказов в каждом децили
SELECT
decile,
COUNT(*) AS orders_cnt,
MIN(total) AS min_total,
MAX(total) AS max_total,
AVG(total) AS avg_total
FROM (
SELECT total, NTILE(10) OVER (ORDER BY total) AS decile
FROM orders
) t
GROUP BY decile
ORDER BY decile;8. Сравнение распределений (A/B тест)
-- сравним распределение сессий в контроле и тесте
SELECT
group_name,
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY session_length) AS p25,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY session_length) AS p50,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY session_length) AS p75,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY session_length) AS p95,
AVG(session_length) AS avg
FROM ab_test_sessions
GROUP BY group_name;Если медиана одинакова, а P95 просел в тесте — значит изменилось поведение «heavy users».
9. Детектирование выбросов
-- IQR метод: выбросы за пределами Q1 - 1.5*IQR и Q3 + 1.5*IQR
WITH stats AS (
SELECT
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total) AS q1,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY total) AS q3
FROM orders
)
SELECT o.* FROM orders o, stats s
WHERE o.total < s.q1 - 1.5 * (s.q3 - s.q1)
OR o.total > s.q3 + 1.5 * (s.q3 - s.q1);10. MySQL <8 альтернатива
Если в версии нет PERCENTILE_CONT:
-- медиана через подзапрос
SELECT AVG(total) AS median
FROM (
SELECT total
FROM orders
ORDER BY total
LIMIT 2 -- для чётного числа
OFFSET (SELECT (COUNT(*) - 1) / 2 FROM orders)
) t;Или через оконные функции (MySQL 8+):
SELECT AVG(total) FROM (
SELECT total,
ROW_NUMBER() OVER (ORDER BY total) AS rn,
COUNT(*) OVER () AS cnt
FROM orders
) t
WHERE rn IN (FLOOR((cnt+1)/2), CEIL((cnt+1)/2));Частые ошибки
Ошибка 1. Использовать AVG вместо медианы
AVG чувствителен к выбросам. Медиана (P50) устойчива.
-- 10 клиентов с чеком 100 ₽ и 1 кит с 1 000 000 ₽
AVG ≈ 100 091 (обман)
Median ≈ 100 (реально)Ошибка 2. Забыть WITHIN GROUP
-- ошибка (в Postgres / Snowflake)
PERCENTILE_CONT(0.5) (ORDER BY total)
-- правильно
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total)Ошибка 3. NULL в PERCENTILE
По умолчанию NULL-значения игнорируются. Если это не то, что нужно, фильтруйте:
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY COALESCE(total, 0))Ошибка 4. Путать NTILE и PERCENTILE
- NTILE(4) → разбивает данные на 4 группы (номер группы 1-4)
- PERCENTILE_CONT(0.25) → возвращает значение P25
Производительность
- PERCENTILE_CONT — тяжёлый на больших данных (требует полной сортировки)
- NTILE — тоже сортировка
- Индекс на целевой столбец ускоряет
Для больших таблиц иногда используют approximate percentiles:
APPROX_PERCENTILEв Snowflakeapprox_quantilesв BigQuery
Связанные темы
FAQ
Как посчитать медиану в SQL?
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col) в Postgres / Oracle / Snowflake. В MySQL 5 — через подзапрос.
P95 или P99?
P95 для большинства задач. P99 — для SRE / performance, где важны редкие «плохие» случаи.
Что такое IQR?
Inter-Quartile Range = P75 − P25. Используется для детектирования выбросов и описания распределения.
Можно ли считать percentile по группе?
Да: PERCENTILE_CONT(0.5) WITHIN GROUP (...) OVER (PARTITION BY group_col) или через GROUP BY.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.