Как посчитать медиану в SQL

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Средний чек и средняя зарплата — показатели с тяжёлыми хвостами, где среднее врёт. Медиана устойчивее к выбросам, потому что делит выборку пополам по количеству. Отсюда частый вопрос на собесах: «как посчитать медиану в SQL».

Задача нетривиальная — в большинстве диалектов нет MEDIAN() как aggregate. Надо знать 2-3 способа в зависимости от движка.

Способ 1: PERCENTILE_CONT (Postgres, MSSQL, BigQuery)

Самый простой:

SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median
FROM employees;

PERCENTILE_CONT(0.5) = медиана. Интерполирует между двумя средними значениями при чётном N.

Способ 2: PERCENTILE_DISC

SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary) AS median
FROM employees;

Отличие: возвращает существующее значение, не интерполирует. Часто эквивалентно на больших данных.

Способ 3: ROW_NUMBER (универсальный)

Работает везде:

WITH ordered AS (
    SELECT salary,
           ROW_NUMBER() OVER (ORDER BY salary) AS rn,
           COUNT(*) OVER () AS n
    FROM employees
)
SELECT AVG(salary) AS median
FROM ordered
WHERE rn IN ((n+1)/2, (n+2)/2);

Для чётного N берёт среднее двух серединных. Для нечётного — одну строку.

Способ 4: NTILE

Поделить на 2 группы:

WITH halves AS (
    SELECT salary, NTILE(2) OVER (ORDER BY salary) AS half
    FROM employees
)
SELECT MAX(CASE WHEN half = 1 THEN salary END) AS median
FROM halves;

Приблизительный способ. Для exact — используйте ROW_NUMBER.

ClickHouse

SELECT quantile(0.5)(salary) FROM employees;

Или median():

SELECT median(salary) FROM employees;

Быстро, специально оптимизировано.

MySQL

До 8.0 нет window functions → через variables:

SELECT AVG(salary) AS median FROM (
    SELECT salary, @rn := @rn + 1 AS rn, @total := (SELECT COUNT(*) FROM employees) AS n
    FROM employees, (SELECT @rn := 0) r
    ORDER BY salary
) t
WHERE rn IN (FLOOR((n+1)/2), CEIL((n+1)/2));

В 8.0+ — ROW_NUMBER как в Postgres.

Медиана по группам

Медиана зарплаты по department:

SELECT department,
       PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median
FROM employees
GROUP BY department;

В одну строку.

На собесе

Задача: «Посчитай медиану зарплаты в таблице employees».

Junior: «В Postgres — PERCENTILE_CONT». Middle: «А если MySQL до 8? Через ROW_NUMBER и FLOOR/CEIL от (N+1)/2». Senior: «Для approximate — можно NTILE или quantile в ClickHouse, trade-off между точностью и скоростью».

Связанные темы

FAQ

MEDIAN() есть где-то?

Oracle, Snowflake — да. Большинство движков — нет.

Почему не использовать AVG?

AVG — среднее, не медиана. На данных с outliers даёт разный результат.

Percentile_cont или percentile_disc?

_Cont интерполирует (точнее на малых данных). _Disc возвращает существующее значение.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.