@ Андроник указал лучшие «стратегии обхода проблемы», но для общего решения нужны полиморфные типы данных c и немного точной настройки. В версии 9.3+ PostgreSQL мы также можем оптимизировать агрегированное исчисление на windows (режим с подвижным агрегированием), что важно для обходного пути.
Это правильное решение
Это Решение - лучшее, что мы можем сделать в настоящее время (2020 год), это всего лишь «хороший и надежный обходной путь»: пожалуйста, сообщите PostgreSQL разработчикам (потребление ОЗУ и ЦП).
create or replace FUNCTION smallseq_agg_sfunc (
state anyarray, data anyelement
) RETURNS anyarray as $f$
SELECT state || data
$f$ language SQL IMMUTABLE;
create or replace FUNCTION array_median(state anyarray) returns anyelement as $f$
SELECT percentile_cont(0.5) within group (ORDER BY s)
FROM unnest(state) t(s)
$f$ language SQL IMMUTABLE;
create or replace AGGREGATE smallset_median (anyelement) (
sfunc = smallseq_agg_sfunc,
stype = anyarray,
finalfunc = array_median,
initcond = '{}'
);
Префикс имени, такой как smallset_
, важен, чтобы помнить, что это обходной путь, действительный только для небольших наборов или упорядоченных небольших последовательностей . Когда он маленький, нет проблем в переупорядочении упорядоченной последовательности. В контексте Больших Данных (больших windows или больших таблиц) мы должны проверить производительность этого обходного пути .
Порядок, характеризующий медианную операцию, очень важен и в выражении window или GROUP BY
обычно смешивается много переменных (много порядков), поэтому order by
полезен в функции библиотеки общего пользования. Вы можете заменить AVG без страха!
Didacti c комментарии
Математики определяют среднее значение как
поэтому мы можем реализовать его и избежать условия order by
для упорядоченных последовательностей:
create or replace FUNCTION smallseq_percentile_cont (
state anyarray
) returns double precision as $f$
SELECT ( s[floor((up+1)*0.5)] + s[ceil((up+1)*0.5)] )::double precision / 2.
FROM ( SELECT array_agg(x) FROM unnest(state) t(x)) ) t1(s)
, ( SELECT array_upper(state,1) ) t2(up)
$f$ language SQL IMMUTABLE;
Пожалуйста, избегайте упрощения на (s[(up + 1)/ 2] + s[(up + 2) / 2])
, это не правильно. Вы можете заменить 0.5
параметром для получения аналога percentile_cont(fraction)
. Для неупорядоченных множеств (smallset_percentile_cont
реализация) вы можете добавить ORDER BY x
после t(x)
.
Более общие случаи
Важно помнить, что "classi c median" основан на на непрерывный процентиль (продолжение), но есть также дискретный процентиль (dis c).
Встроенная функция percentile_cont()
не имеет смысла для text
типа данных и других, а иногда нам нужно сохранить медиану в том же типе данных (например, целое число, а не двойное) или использовать элемент выборки. В этом контексте мы думаем о percentile_disc()
. Предположим также, что в качестве полезной универсальной функции массива c мы предпочитаем параметр c one:
create or replace FUNCTION array_percentile_disc(
state anyarray, p float DEFAULT 0.5
) returns anyelement as $f$
SELECT percentile_disc(p) within group (ORDER BY s)
FROM unnest(state) t(s)
$f$ language SQL IMMUTABLE;
create or replace FUNCTION array_median_disc(state anyarray)
returns anyelement as $wrap$
SELECT array_percentile_disc($1)
$wrap$ language SQL IMMUTABLE;
create or replace AGGREGATE smallset_median_disc (anyelement) (
sfunc = smallseq_agg_sfunc,
stype = anyarray,
finalfunc = array_median_disc,
initcond = '{}'
);
Есть еще одна проблема с PostgreSQL : невозможно определить второй параметр для finalfun c, поэтому, если вам нужно, например, определить AGGREGATE smallseq_percentile_disc(anyelement,float)
, вам нужно определить каждый, например, для «90% дискретных», определить больше одна функция обтекания array_perc90_disc()
с использованием array_percentile_disc($1,0.9)
.
Опция режима движущегося агрегирования
См. Руководство по PG .
... пожалуйста, совместите здесь : этот ответ Wiki!
Временное решение для больших данных
Как и предположил @Andronicus, мы можем использовать lag()
. В этом случае важно также помнить, чтобы проверить порядок окна, возможно, вам нужно указать c окно для медианы.
SELECT x, avg_x,
CASE WHEN w_count<9001 THEN NULL ELSE mdn_x END mdn_x
FROM (
SELECT x,
count(*) over w AS w_count,
avg(x) over w AS avg_x,
(
lag(x, floor(9002*0.5)::double precision / 2.) over w
+ lag(x, ceil(9002*0.5)::double precision / 2.) over w
) / 2. AS mdn_x
FROM t
WINDOW w as (ORDER BY x rows between 9000 preceding and current row)
) t_aux
Мы можем использовать w_count
, чтобы обобщить и избежать начальных нулей, и теперь, предполагая также «дискретную медиану», мы можем использовать:
SELECT x, w_count, avg_x,
lag( x, floor(w_count*0.5) ) over w2 AS mdn_x
FROM (
SELECT x,
count(*) over w AS w_count,
avg(x) over w AS avg_x
FROM t
WINDOW w1 as (rows between 9000 preceding and current row)
) t_aux
WINDOW w2 as (ORDER BY x rows between 9000 preceding and current row)
Важно, что w2
является клоном w1
, за исключением предложения ORDER.