@Andronicus 指出了最好的“变通策略”,但一般解决方案需要多态数据类型和一些精细调整。使用 PostgreSQL 9.3+ 版本,我们还可以优化 windows 上的聚合计算(移动聚合模式),这对于解决方法很重要。
这是正确的解决方案
这个解决方案是我们现在(2020 年)能做的最好的解决方案,它只是一个“好的和可靠的解决方法”:请让 PostgreSQL 开发人员知道(RAM 和 CPU 消耗)。
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_ 这样的名称前缀很重要,记住它是一种变通方法,仅对小集合 或有序小序列 有效。当它很小时,重新排序有序序列没有问题。在大数据(大窗口或大表)的上下文中,我们必须check performance of this workaround。
顺序表征中位数运算,非常重要,在window 或GROUP BY 子句中通常混合许多变量(许多顺序),因此order by 在通用函数中很有用图书馆。你可以replace AVG无所畏惧!
教学法
数学家define the mean as
所以,我们可以实现它并避免有序序列的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 实现),您可以在t(x) 之后添加ORDER BY x。
更一般的情况
重要的是要记住,“经典中位数”基于连续百分位数(续),但也有离散百分位数(圆盘)。
内置函数 percentile_cont() 对 text 数据类型和其他数据类型没有意义,有时我们需要在同一数据类型中保留中位数(例如整数而不是双精度),或者使用示例元素。在这种情况下,我们正在考虑percentile_disc()。还假设,作为有用的通用数组函数,我们更喜欢参数函数:
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 = '{}'
);
有another problem with PostgreSQL:不可能为finalfunc定义一个第二个参数,所以,如果你需要例如定义AGGREGATE smallseq_percentile_disc(anyelement,float)你需要定义每一个,例如为“ 90% 离散" 使用array_percentile_disc($1,0.9) 定义更多的包装函数array_perc90_disc()。
移动聚合模式选项
见Pg Guide。
...请在此处合作:此答案是 Wiki!
大数据解决方法
正如@Andronicus 建议的那样,我们可以使用lag()。在这种情况下,记住检查窗口的顺序也很重要,也许您需要一个特定的窗口作为中位数。
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 子句。