9

有没有人发现 PostgreSQL 等效于 Oracle 的 PERCENTILE_CONT 函数?找了找,没找到,就自己写了。

这是我希望对您有所帮助的解决方案。

我工作的公司希望将 Java EE Web 应用程序从使用 Oracle 数据库迁移到使用 PostgreSQL。几个存储过程严重依赖于使用 Oracle 独特的 PERCENTILE_CONT() 函数。这个函数在 PostgreSQL 中不存在。

我试图搜索是否有人将该功能“移植”到 PG 中,但无济于事。

4

2 回答 2

18

经过更多搜索,我找到了一个页面,其中列出了 Oracle 如何在以下位置实现此功能的伪代码:

http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions110.htm

我决定在 PG 中编写自己的函数来模仿 Oracle 的特性。

我在::: 找到了 David Fetter 的数组排序技术

http://postgres.cz/wiki/PostgreSQL_SQL_Tricks#General_array_sort

排序数组元素

这里(为了清楚起见)是大卫的代码:

CREATE OR REPLACE FUNCTION array_sort (ANYARRAY)
RETURNS ANYARRAY LANGUAGE SQL
AS $$
SELECT ARRAY(
    SELECT $1[s.i] AS "foo"
    FROM
        generate_series(array_lower($1,1), array_upper($1,1)) AS s(i)
    ORDER BY foo
);
$$;

所以这是我写的函数:

CREATE OR REPLACE FUNCTION percentile_cont(myarray real[], percentile real)
RETURNS real AS
$$

DECLARE
  ary_cnt INTEGER;
  row_num real;
  crn real;
  frn real;
  calc_result real;
  new_array real[];
BEGIN
  ary_cnt = array_length(myarray,1);
  row_num = 1 + ( percentile * ( ary_cnt - 1 ));
  new_array = array_sort(myarray);

  crn = ceiling(row_num);
  frn = floor(row_num);

  if crn = frn and frn = row_num then
    calc_result = new_array[row_num];
  else
    calc_result = (crn - row_num) * new_array[frn] 
            + (row_num - frn) * new_array[crn];
  end if;

  RETURN calc_result;
END;
$$
  LANGUAGE 'plpgsql' IMMUTABLE;

以下是一些比较测试的结果:

CREATE TABLE testdata
(
  intcolumn bigint,
  fltcolumn real
);

这是测试数据:

insert into testdata(intcolumn, fltcolumn)  values  (5, 5.1345);
insert into testdata(intcolumn, fltcolumn)  values  (195, 195.1345);
insert into testdata(intcolumn, fltcolumn)  values  (1095, 1095.1345);
insert into testdata(intcolumn, fltcolumn)  values  (5995, 5995.1345);
insert into testdata(intcolumn, fltcolumn)  values  (15, 15.1345);
insert into testdata(intcolumn, fltcolumn)  values  (25, 25.1345);
insert into testdata(intcolumn, fltcolumn)  values  (495, 495.1345);
insert into testdata(intcolumn, fltcolumn)  values  (35, 35.1345);
insert into testdata(intcolumn, fltcolumn)  values  (695, 695.1345);
insert into testdata(intcolumn, fltcolumn)  values  (595, 595.1345);
insert into testdata(intcolumn, fltcolumn)  values  (35, 35.1345);
insert into testdata(intcolumn, fltcolumn)  values  (30195, 30195.1345);
insert into testdata(intcolumn, fltcolumn)  values  (165, 165.1345);
insert into testdata(intcolumn, fltcolumn)  values  (65, 65.1345);
insert into testdata(intcolumn, fltcolumn)  values  (955, 955.1345);
insert into testdata(intcolumn, fltcolumn)  values  (135, 135.1345);
insert into testdata(intcolumn, fltcolumn)  values  (19195, 19195.1345);
insert into testdata(intcolumn, fltcolumn)  values  (145, 145.1345);
insert into testdata(intcolumn, fltcolumn)  values  (85, 85.1345);
insert into testdata(intcolumn, fltcolumn)  values  (455, 455.1345);

以下是比较结果:

ORACLE RESULTS
ORACLE RESULTS

select  percentile_cont(.25) within group (order by fltcolumn asc) myresult
from testdata;
select  percentile_cont(.75) within group (order by fltcolumn asc) myresult
from testdata;

myresult
- - - - - - - -
57.6345                

myresult
- - - - - - - -
760.1345               

POSTGRESQL RESULTS
POSTGRESQL RESULTS

select percentile_cont(array_agg(fltcolumn), 0.25) as myresult
from testdata;

select percentile_cont(array_agg(fltcolumn), 0.75) as myresult
from testdata;

myresult
real
57.6345

myresult
real
760.135

我希望这可以帮助某人不必重新发明轮子。

享受!雷·哈里斯

于 2013-01-13T23:15:24.383 回答
4

在 PostgreSQL 9.4 中,现在有对百分位数的原生支持,在Ordered-Set Aggregate Functions中实现:

percentile_cont(fraction) WITHIN GROUP (ORDER BY sort_expression) 

连续百分位数:返回与排序中指定分数相对应的值,如果需要,在相邻输入项之间进行插值

percentile_cont(fractions) WITHIN GROUP (ORDER BY sort_expression)

多个连续百分位数:返回与分数参数的形状匹配的结果数组,每个非空元素替换为对应于该百分位数的值

有关更多详细信息,请参阅文档:http ://www.postgresql.org/docs/current/static/functions-aggregate.html

于 2015-02-16T22:48:07.267 回答