Некоторые базы данных достаточно MEDIAN()
для реализации агрегатной функции MEDIAN()
. Помните, что MEDIAN()
слегка отличается от (и часто более полезен, чем) MEAN()
или AVG()
(в среднем).
В то время как среднее значение рассчитывается как SUM(exp) / COUNT(exp)
, MEDIAN()
сообщает, что 50% всех значений в выборке выше, чем MEDIAN()
тогда как остальные 50% набора ниже, чем MEDIAN()
.
Итак, другими словами, если вы берете следующий запрос:
01
02
03
04
05
06
07
08
09
10
|
WITH t(value) AS ( SELECT 1 FROM DUAL UNION ALL SELECT 2 FROM DUAL UNION ALL SELECT 3 FROM DUAL ) SELECT avg (value), median(value) FROM t; |
… Тогда и среднее, и среднее значение одинаковы:
1
2
|
avg median 2 2 |
Но если вы сильно искажаете свои данные следующим образом:
01
02
03
04
05
06
07
08
09
10
|
WITH t(value) AS ( SELECT 1 FROM DUAL UNION ALL SELECT 2 FROM DUAL UNION ALL SELECT 100 FROM DUAL ) SELECT avg (value), median(value) FROM t; |
Тогда ваше среднее значение также будет искажено, в то время как медиана все равно будет указывать, где находится большинство значений в вашей выборке.
1
2
|
avg median 34.333 2 |
Приведенный выше пример, конечно, статистически незначим, но вы можете легко увидеть, что эффект может быть существенным и значительным, если у вас есть больше данных:
Эффект асимметрии очень важен в статистике, и для того, чтобы сделать что-либо интересное, использование процентилей чаще всего более полезно, чем использование средних значений. Возьмем, к примеру, средний доход по сравнению со средним доходом в стране. В то время как средний доход в США (и во многих других странах) неуклонно растет, средний доход за последнее десятилетие снизился . Это связано с тем, что богатство все больше и больше склоняется к сверхбогатым.
Этот блог не о политике, а о Java и SQL, поэтому давайте вернемся к подсчету фактов.
Использование прецентилей в SQL
Как мы уже видели, MEDIAN()
делит выборку на две группы одинакового размера и принимает значение «между» этими двумя группами. Это конкретное значение также называется 50-й процентиль, потому что 50% всех значений в выборке ниже, чем MEDIAN()
. Таким образом, мы можем установить:
-
MIN(exp)
: 0-процентиль -
MEDIAN(exp)
: 50-й процентиль -
MAX(exp)
: сотый процентиль
Все вышеперечисленное является частным случаем процентилей, и хотя MIN()
и MAX()
поддерживаются во всех базах данных SQL (и стандарте SQL), MEDIAN()
не входит в стандарт SQL и поддерживается только следующими базами данных jOOQ :
- CUBRID
- HSQLDB
- оракул
- Sybase SQL Anywhere
Существует другой способ вычисления MEDIAN()
в частности и любого вида процентиля в целом в стандарте SQL, и, поскольку PostgreSQL 9.4 также в PostgreSQL, используя…
Упорядоченно-заданные агрегатные функции
Интересно, что помимо оконных функций вы также можете указать предложения ORDER BY
для определенных агрегатных функций, которые агрегируют данные на основе упорядоченных наборов.
Одной из таких функций является стандартная функция percentile_cont
в SQL, которая принимает процентиль в качестве аргумента, а затем принимает дополнительное предложение WITHIN GROUP
, которое принимает предложение ORDER BY
в качестве аргумента. Эти конкретные функции упорядоченного набора также называют обратными функциями распределения , потому что мы хотим найти, где находится конкретный процентиль в распределении всех значений в образце ( если вас не пугает математика, ознакомьтесь со статьей в Википедии )
Итак, в PostgreSQL 9.4+ функцию MEDIAN MEDIAN()
можно эмулировать так:
01
02
03
04
05
06
07
08
09
10
|
WITH t(value) AS ( SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 100 ) SELECT avg (value), percentile_cont(0.5) WITHIN GROUP ( ORDER BY value) FROM t; |
Этот интересный синтаксис стандартизирован и может быть известен некоторым из вас из Oracle LISTAGG () , который позволяет объединять значения в объединенные строки:
1
2
3
4
5
6
7
8
9
|
WITH t(value) AS ( SELECT 1 FROM DUAL UNION ALL SELECT 2 FROM DUAL UNION ALL SELECT 100 FROM DUAL ) SELECT listagg(value, ', ' ) WITHIN GROUP ( ORDER BY value) FROM t; |
Этот запрос дает просто:
1
2
3
|
listagg --------- 1, 2, 100 |
С другой стороны: LISTAGG()
, конечно, совершенно бесполезен, потому что он возвращает VARCHAR2
, который снова имеет максимальную длину 4000 в Oracle. Бесполезный…
Эмуляция из коробки с JOOQ
Как всегда, JOOQ будет эмулировать подобные вещи из коробки. Вы можете использовать либо DSL.median()
, либо с выходом jOOQ 3.6 новую DSL.percentileCont()
для получения того же значения:
1
2
3
4
5
6
7
|
DSL.using(configuration) . select ( median(T.VALUE), percentileCont(0.5).withinGroupOrderBy(T.VALUE) ) . from (T) . fetch (); |
Ссылка: | Как эмулировать агрегатную функцию MEDIAN () с использованием функций обратного распределения от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ . |