Kingbase ES 自定义聚合函数和一次改写案例

发布时间 2023-09-18 16:19:06作者: KINGBASE研究院

文章概要:

KES的SQL的语法暂时不兼容oracle的自定义聚合函数的创建语法和流程,但是可以使用KES已支持的语法改写。
本文整理和简单解析了自定义聚合函数的原理和解读了范例代码。
并根据客户代码进行了改写。

一,oracle自定义聚合函数的简析

oracle的自定义聚合函数需要实现4个ODCIAggregate 接口函数,被声明和定义在一个对象类型中。
这些函数定义了任何一个聚集函数内部需要实现的操作,
这些函数分别是 initialization, iteration, merging(定义了并行聚合enable parallel时会调用) 和 termination。如下图是自定义聚合函数的处理流程:

1,自定义聚合函数的并行流程:

               |--------------|
               |              |
              \|/             |
ODCIAggregateInitialize ------->  ODCIAggregateIterate
                        |
                        |
                        | -----> ODCIAGGREGATEMERGE -----> ODCIAGGREGATETERMINATE
                        |
               |--------------| 
               |              |
              \|/             | 
ODCIAggregateInitialize ------->  ODCIAggregateIterate

自定义聚合函数的串行流程(实测不会调用ODCIAGGREGATEMERGE):

               |--------------|
               |              |
              \|/             |
ODCIAggregateInitialize------->   ODCIAggregateIterate -----> ODCIAGGREGATETERMINATE

从上可以看出,并行是会调用ODCIAGGREGATEMERGE,但是非并行未调用ODCIAGGREGATEMERGE。

2,oracle自定义聚合函数的实例解析


举一个计算第二大值得案例为例

接口的定义:

create type SecondMaxImpl as object
(
     --自定义保存最大值
     max NUMBER, 
     --自定义保存第二大值
     secmax NUMBER, 
     --初始化函数,必须要实现的方法,用于在聚合运算的最开始部分,初始化上下文环境  
     static function ODCIAggregateInitialize(sctx IN OUT SecondMaxImpl) return number,
     --迭代运算函数,oracle依据该函数进行迭代运算,
     --第一个参数self,为聚合运算的上下文,  
     --第二个参数value,为当前需要处理的值,可以为number varchar2等类型,  
     --在迭代过程中,如果当前值为null,则忽略该次迭代  
     member function ODCIAggregateIterate(self IN OUT SecondMaxImpl, value IN number) return number,
     --(oracle会有选择执行该步骤)该函数用于合并两个上下文到一个上下文中,在并行和串行环境下均有可能发挥作用 
     member function ODCIAggregateMerge(self IN OUT SecondMaxImpl, ctx2 IN SecondMaxImpl) return number,
     --该函数在聚合运算的最后一步运行,用于对结果进行处理并返回处理结果,  
     --第一个参数self为上下文,
     --第二个参数returnValue为返回值,可以为number,varchar2等类型  
     --第三个参数flags为标识位 
     member function ODCIAggregateTerminate(self IN SecondMaxImpl, returnValue OUT number, flags IN number) return number
);
/

--聚合函数的定义和接口实现

create or replace type body SecondMaxImpl is 
static function ODCIAggregateInitialize(sctx IN OUT SecondMaxImpl) 
return number is 
begin
   sctx := SecondMaxImpl(0, 0); --初始化
   return ODCIConst.Success;
end;

member function ODCIAggregateIterate(
    self IN OUT SecondMaxImpl, 
    value IN number
) 
return number 
is
begin
 if value > self.max then
    self.secmax := self.max;
    self.max := value;
 elsif value > self.secmax then
    self.secmax := value;
 end if;
 return ODCIConst.Success;
end;

member function ODCIAggregateMerge(
    self IN OUT SecondMaxImpl,  --合并两个上下文(并行聚合有用。只能用于并行聚合?)
    ctx2 IN SecondMaxImpl
) 
return number 
is
begin
   if ctx2.max > self.max then
       if ctx2.secmax > self.secmax then 
           self.secmax := ctx2.secmax;
       else
           self.secmax := self.max;
       end if;
       self.max := ctx2.max;
   elsif ctx2.max > self.secmax then
       self.secmax := ctx2.max;
   end if;
   return ODCIConst.Success;
end;

member function ODCIAggregateTerminate(
    self IN SecondMaxImpl, 
    returnValue OUT number, 
    flags IN number
) 
return number is
begin
 returnValue := self.secmax; --获取第二大值
 --returnValue := self.max; --获取最大值
 return ODCIConst.Success;
end;
end;
/
---创建聚合函数
CREATE FUNCTION SecondMax (input NUMBER) RETURN NUMBER 
PARALLEL_ENABLE AGGREGATE USING SecondMaxImpl;

--构造测试数据,看看运行效果

create table tt02(
    id int , 
    name varchar(20), 
    sal number(6,2)
);

insert into tt02 values(12,'aaaa',1253.2);
insert into tt02 values(14,'aass',1965.3);
insert into tt02 values(13,'vvvv',2056.6);
insert into tt02 values(16,'aass',6562.1);
insert into tt02 values(15,'aass',4563.4);
insert into tt02 values(12,'aaaa',2532.2);
insert into tt02 values(14,'aass',1965.3);
insert into tt02 values(13,'vvvv',6556.6);
insert into tt02 values(16,'aass',8965.1);
insert into tt02 values(15,'aass',7854.4);

--测试验证(符合预期)

SQL> select id,SecondMax(sal) from tt02 group by id;

    ID SECONDMAX(SAL)
---------- --------------
    12     1253.2
    13     2056.6
    14     1965.3
    16     6562.1

SQL> select /*+ parallel */ id,SecondMax(sal) from tt02 group by id;

    ID SECONDMAX(SAL)
---------- --------------
    12     1253.2    
    13     2056.6    
    14     1965.3    
    16     6562.1

--重新生成数据

delete from tt02;

declare
    vv1 int;
    vv2 int;
    vv3 varchar2(20);
begin
    for i in 1..10 loop
        select abs(mod(dbms_random.random,10000)) into vv1 from dual;
        select abs(mod(dbms_random.random,5)) into vv2 from dual;
        select dbms_random.string('u',5) into vv3 from dual; 
        insert into tt02 values(vv2,vv3,vv1);
    end loop;
end;

SQL> select * from tt02;

    ID NAME                      SAL
---------- ---------------------------------------- ----------
     3 IJTIJ                     5978
     4 JUAWT                     2087
     0 FPBMN                     2218
     0 AMBNZ                      618
     2 VFXJD                      37
     4 PEXGM                     7983
     2 CDQJT                      877
     2 ENQFX                     3359
     2 ICJFI                     1220
     0 XCGJX                     1397

10 rows selected.

--二次验证(符合预期)

SQL> select id,SecondMax(sal) from tt02 group by id;

    ID SECONDMAX(SAL)
---------- --------------
     0      1397
     2      1220   
     3       0   
     4      2087

SQL> select /*+ parallel */ id,SecondMax(sal) from tt02 group by id;

    ID SECONDMAX(SAL)
---------- --------------
     0      1397   
     2      1220   
     3       0   
     4      2087

实际上就改写而言,到此已经知道该函数的作用

二,KES语法改写oracle自定义聚合函数

1,oracle的自定义聚合函数定义及其分析验证

oracle自定义聚合函数sumc2如下,先对其进行一个简单的分析和功能验证:

--对象类型头(接口声明)
CREATE OR REPLACE EDITIONABLE TYPE "TYPESUMVARCHAR2" as object (
  vsum VARCHAR2(4000),
  --自定义聚集函数初始化设置,从这儿开始一个聚集函数
  static function ODCIAggregateInitialize(sctx IN OUT TYPESUMVARCHAR2) return number,
  --自定义聚集函数,最主要的步骤,这个函数定义我们的聚集函数具体做什么操作,后面的例子,是取最大值,最小值,平均值,还是做连接操作.self 为当前聚集函数的指针,用来与前面的计算结果进行关联
  member function ODCIAggregateIterate(self IN OUT TYPESUMVARCHAR2,value IN varchar2) return number,
  --终止聚集函数的处理,返回聚集函数处理的结果
  member function ODCIAggregateTerminate(self IN TYPESUMVARCHAR2,returnValue OUT VARCHAR2, flags IN number) return number,
  --用来合并两个聚集函数的两个不同的指针对应的结果,用户合并不同结果结的数据,特别是处理并行(parallel)查询聚集函数的时候.
  member function ODCIAggregateMerge(self IN OUT TYPESUMVARCHAR2,ctx2 IN TYPESUMVARCHAR2) return number

);

对象类型体(接口定义)

CREATE OR REPLACE EDITIONABLE TYPE BODY "TYPESUMVARCHAR2" IS
    --自定义聚集函数初始化设置,从这儿开始一个聚集函数
    STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
        SCTX IN OUT TYPESUMVARCHAR2
    )
    RETURN NUMBER IS
    BEGIN
        SCTX := TYPESUMVARCHAR2('');
        --初始化vsum
        RETURN ODCICONST.SUCCESS;
    END;
    --自定义聚集函数,最主要的步骤,这个函数定义我们的聚集函数具体做什么操作,后面的例子,
    --是取最大值,最小值,平均值,还是做连接操作.self 为当前聚集函数的指针,用来与前面的计算结果进行关联
    MEMBER FUNCTION ODCIAGGREGATEITERATE(
        SELF IN OUT TYPESUMVARCHAR2,
        VALUE IN VARCHAR2
    ) RETURN NUMBER IS
    BEGIN
        --SELF.vsum := substr(SELF.vsum || ',' || VALUE,0,2000);
        IF instr(
            SELF.vsum,
            value
        ) = 0
        OR SELF.vsum IS NULL THEN
        SELF.vsum := substr(
            SELF.vsum || ',' || VALUE,
            0,
            2000
        ) ;
        ELSE
        SELF.vsum := substr(
            SELF.vsum || '',
            0,
            2000
        ) ;
        END IF ;
        RETURN ODCICONST.SUCCESS;
    END;
    --终止聚集函数的处理,返回聚集函数处理的结果,(代码做了截取)
    MEMBER FUNCTION ODCIAGGREGATETERMINATE(
        SELF IN TYPESUMVARCHAR2,
    RETURNVALUE OUT VARCHAR2,
        FLAGS IN NUMBER
    ) RETURN NUMBER IS
    BEGIN
        RETURNVALUE := SUBSTR(
            SELF.vsum,
            2
        );

        RETURN ODCICONST.SUCCESS;
    END;
    --用来合并两个聚集函数的两个不同的指针对应的结果,用户合并不同结果结的数据,特别是处理并行(parallel)查询聚集函数的时候.
    --此处默认未做任何动作(未执行合并)
    MEMBER FUNCTION ODCIAGGREGATEMERGE(
        SELF IN OUT TYPESUMVARCHAR2,
        CTX2 IN TYPESUMVARCHAR2
    ) 
    RETURN NUMBER IS
    BEGIN
        RETURN ODCICONST.SUCCESS;
    END;

END;

--创建自定义聚合函数

CREATE OR REPLACE FUNCTION  sumc2(input varchar2) 
RETURN varchar2
PARALLEL_ENABLE AGGREGATE 
USING TYPESUMVARCHAR2;

根据上面的tt02表实测

SQL> select * from tt02;

    ID NAME                      SAL
---------- ---------------------------------------- ----------
     3 IJTIJ                     5978
     4 JUAWT                     2087
     0 FPBMN                     2218
     0 AMBNZ                      618
     2 VFXJD                      37
     4 PEXGM                     7983
     2 CDQJT                      877
     2 ENQFX                     3359
     2 ICJFI                     1220
     0 XCGJX                     1397

SQL> select id,sumc2(name) from tt02 group by id;

ID SUMC2(NAME)
--------------------------------------------------------------------------------
0 FPBMN,XCGJX,AMBNZ
2 VFXJD,ICJFI,ENQFX,CDQJT
3 IJTIJ
4 JUAWT,PEXGM

更换表实测:

--teachers表如下:
 teacher_id | teacher_name | age |  sal  | gender | title  | position | department
------------+--------------+-----+----------+--------+----------+------------+------------
  10001001 | 陈思宇    | 46 | 15689.00 | 男   | 特级教师 | 校长    | 校长室
  10001002 | 文强     | 44 | 29942.00 | 男   | 特级教师 | 副校长   | 校长室
  10001003 | 吴玲     | 41 | 29142.00 | 女   | 高级教师 | 办公室主任 | 办公室
  10001004 | 章丽     | 41 | 28242.00 | 女   | 高级教师 | 教务处主任 | 教务处
  10001005 | 张志东    | 41 | 28242.00 | 男   | 高级教师 | 财务处主任 | 财务处
  10001006 | 熊浩宇    | 49 | 28356.00 | 女   | 一级教师 | 招生办主任 | 招生办
  10001007 | 朱雯     | 49 | 24016.00 | 女   | 一级教师 | 招生办助理 | 招生办
  10001008 | 张志强    | 49 | 23964.00 | 女   | 一级教师 | 财务处助理 | 财务处
  10001009 | 朱国斌    | 49 | 21974.00 | 男   | 二级教师 | 财务处助理 | 财务处

oracle测试结果

select age,sumc2(title) from teachers group by age;
41 高级教师
44 特级教师
46 特级教师
49 一级教师,二级教师

通过代码和实际效果的实测,聚合函数sumc2作用是去重,获取去重后的拼接结果。
了解了客户原生代码和oracle自定义聚合功能后,

2,KES所支持的聚合函数基本原理

KES能支持的聚合函数(PLPGSQL)语法如下:

CREATE AGGREGATE name ( [ argmode ] [ argname ] arg_data_type [ , ... ] ) (  
  SFUNC = sfunc,    ---迭代函数,每行数据迭代调用计算结果
  STYPE = state_data_type    ----聚合函数返回的数据类型
  [ , SSPACE = state_data_size ]  
  [ , FINALFUNC = ffunc ]   ----每组的最终计算函数,可选
  [ , FINALFUNC_EXTRA ]  
  [ , COMBINEFUNC = combinefunc ]   ---聚合COMBINEFUNC函数,开启后在开启PARALLEL能并行
  [ , SERIALFUNC = serialfunc ]  
  [ , DESERIALFUNC = deserialfunc ]  
  [ , INITCOND = initial_condition ]  ---INITCOND是第一次调用SFUNC给第一个参数的传值,可以不写。
  [ , MSFUNC = msfunc ]  
  [ , MINVFUNC = minvfunc ]  
  [ , MSTYPE = mstate_data_type ]  
  [ , MSSPACE = mstate_data_size ]  
  [ , MFINALFUNC = mffunc ]  
  [ , MFINALFUNC_EXTRA ]  
  [ , MINITCOND = minitial_condition ]  
  [ , SORTOP = sort_operator ]  
  [ , PARALLEL = { SAFE | RESTRICTED | UNSAFE } ]   ----SAFE并行聚合
)  

其中重点需要讲的是:

聚合函数是每组独立计算的,比如按上述teachers表的age聚合(GROUP BY age),那么就会分4组,4组分别内部进行计算。

--teachers表如下:
 teacher_id | teacher_name | age |  sal  | gender | title  | position | department
------------+--------------+-----+----------+--------+----------+------------+------------
  10001001 | 陈思宇    | 46 | 15689.00 | 男   | 特级教师 | 校长    | 校长室
  10001002 | 文强     | 44 | 29942.00 | 男   | 特级教师 | 副校长   | 校长室
  10001003 | 吴玲     | 41 | 29142.00 | 女   | 高级教师 | 办公室主任 | 办公室
  10001004 | 章丽     | 41 | 28242.00 | 女   | 高级教师 | 教务处主任 | 教务处
  10001005 | 张志东    | 41 | 28242.00 | 男   | 高级教师 | 财务处主任 | 财务处
  10001006 | 熊浩宇    | 49 | 28356.00 | 女   | 一级教师 | 招生办主任 | 招生办
  10001007 | 朱雯     | 49 | 24016.00 | 女   | 一级教师 | 招生办助理 | 招生办
  10001008 | 张志强    | 49 | 23964.00 | 女   | 一级教师 | 财务处助理 | 财务处
  10001009 | 朱国斌    | 49 | 21974.00 | 男   | 二级教师 | 财务处助理 | 财务处

1),SFUNC迭代函数,假设自定义如下:

CREATE OR REPLACE FUNCTION YOUR_SFUNC_NAME (numeric, numeric, numeric......)
RETURNS numeric
as
begin
    .......(省略)
end

这个函数就是每行数据的迭代函数

参数一:$1, 上一次迭代的计算结果;
参数二:$2, YOUR_AGGREGATE_NAME的第一个参数(如果聚合函数传入的是表的列,则表示当前行数据)
参数三:$3, YOUR_AGGREGATE_NAME的第二个参数(如果是个固定值,比如数值2,则每次传入2)
参数四:$4, YOUR_AGGREGATE_NAME的第三个参数

...........

2),FINALFUNC最终函数,假设自定义如下:

CREATE OR REPLACE FUNCTION YOUR_FINALFUNC_NAME (numeric)    ---只有一个参数,SFUNC函数返回的是numeric,所以这里是numeric
RETURNS numeric      ---聚合返回类型,所以这里是numeric
as
begin
    .......(省略)
end

通过YOUR_SFUNC_NAME函数将每组计算完后,,最后调用一次,也就是说最后还可以进行一次规则计算。

3),YOUR_AGGREGATE_NAME聚合函数
聚合函数的定义会引用到上述YOUR_SFUNC_NAME和YOUR_FINALFUNC_NAME函数,最终达成特定目标的计算。

一个典型的非并行使用举例;

CREATE AGGREGATE YOUR_AGGREGATE_NAME(numeric, numeric)    ---接受两个参数,会作为SFUNC函数的后两个参数
(
        INITCOND = xxxx,    ---INITCOND是第一次调用YOUR_SFUNC_NAME函数,给第一个参数的传值xxxx,可以不写。
        STYPE = numeric,     ---聚合函数返回的数据类型numeric
        SFUNC = YOUR_SFUNC_NAME,    ---每组的自定义迭代函数
        FINALFUNC = YOUR_FINALFUNC_NAME   ---每组的小结函数
);

掌握了以上基本语法信息后已经可以编写自定义聚合函数了。

参考于这篇文章:

Postgresql自定义聚合函数入门案例_pgsql自定义聚合函数_高铭杰的博客-CSDN博客

DROP AGGREGATE myavg(integer);
CREATE TABLE t_taxi(trip_id int, km numeric);

insert into t_taxi values (1, 3.4);
insert into t_taxi values (1, 5.3);
insert into t_taxi values (1, 2.9);
insert into t_taxi values (2, 9.3);
insert into t_taxi values (2, 1.6);
insert into t_taxi values (2, 4.3);

 trip_id | km  
---------+-----
       1 | 3.4
       1 | 5.3
       1 | 2.9
       2 | 9.3
       2 | 1.6
       2 | 4.3
————————————————

CREATE OR REPLACE FUNCTION taxi_accum (numeric, numeric, numeric)
RETURNS numeric AS
$$
BEGIN
    RAISE NOTICE 'prev:[%] curr:(%) outer:(%) return:(%)', $1, $2, $3, $1 + $2 * $3;
    RETURN $1 + $2 * $3;
END;
$$
LANGUAGE 'plpgsql';


CREATE OR REPLACE FUNCTION taxi_final (numeric)
RETURNS numeric AS
$$
BEGIN
    RAISE NOTICE 'final:(%) return:(%)', $1, round($1 + 5, -1);
    RETURN round($1 + 5, -1);
END;
$$
LANGUAGE 'plpgsql';

CREATE AGGREGATE taxi(numeric, numeric)
(
        SFUNC = taxi_accum,
        STYPE = numeric,
        FINALFUNC = taxi_final,
        INITCOND = 3.50
);


--测试
test=# SELECT  trip_id, taxi(km, 2.20), 3.50 + sum(km)*2.2 AS manual FROM t_taxi GROUP BY trip_id;
test-# /
NOTICE:  prev:[3.50] curr:(3.4) outer:(2.20) return:(10.980)
NOTICE:  prev:[10.980] curr:(5.3) outer:(2.20) return:(22.640)
NOTICE:  prev:[22.640] curr:(2.9) outer:(2.20) return:(29.020)--对trip_id为1的组进行SFUNC函数迭代计算
NOTICE:  prev:[3.50] curr:(9.3) outer:(2.20) return:(23.960)
NOTICE:  prev:[23.960] curr:(1.6) outer:(2.20) return:(27.480)
NOTICE:  prev:[27.480] curr:(4.3) outer:(2.20) return:(36.940)--对trip_id为2的组进行SFUNC函数迭代计算
NOTICE:  final:(29.020) return:(30)--对trip_id为1的组将SFUNC函数返回的结果(参数第一个值)进行最终计算
NOTICE:  final:(36.940) return:(40)--对trip_id为2的组将SFUNC函数返回的结果(参数第一个值)进行最终计算
 trip_id | taxi | manual
---------+------+--------
       1 |   30 |  29.02
       2 |   40 |  36.94
(2 rows)

Time: 1.775 ms

--可以看到基本的聚合函数(非并行)流程就是
--1,使用SFUNC函数对每组数据进行迭代计算
--2,每组数据计算完成后,使用FINALFUNC将每组的SFUNC结果

3,KES自定义聚合函数改写

使用plpgsql语法改写(对应上述oracle的sumc2函数)后:

--定义每行数据的迭代函数
CREATE OR REPLACE FUNCTION agg(v1 varchar,v2 varchar)
RETURNS varchar IMMUTABLE
AS 
$$
BEGIN
    if v1 is null or LENGTH(v1) = 0 then
        v1 := substr(v2, 0 , 2000) ;
    ELSIF v2 is null and v1 is not null then 
        v1 := substr(v1, 0 , 2000) ;
    ELSIF instr( v1, v2 ) = 0 then  ---如果是不包含关系(则拼接)
        v1 := substr(v1 || ',' || v2 , 0 , 2000) ;
    else
        v1 := substr(v1 , 0 , 2000) ;
    end if ;
    return v1;
end;
$$LANGUAGE 'plpgsql';

--定义每组的最终函数
CREATE OR REPLACE FUNCTION agg_final (varchar)
RETURNS varchar AS
$$
BEGIN
    RETURN $1;     ---本例子无需任何额外处理
END;
$$
LANGUAGE 'plpgsql';

---定义聚合函数
CREATE OR REPLACE AGGREGATE sumc2 (varchar)
(
    SFUNC = agg,   ---每组的自定义迭代函数
    STYPE = varchar,   ---聚合函数返回的数据类型
    FINALFUNC = agg_final,   ---每组的最终函数
    INITCOND =''   --INITCOND是第一次调用agg函数,给第一个参数的传值。
 );

对上述改写后的聚合函数进行基本功能的验证

kes测试:

--建表
CREATE table teachers (
  teacher_id number(8) not null,
  teacher_name varchar2 (40) not null,
  age number(2),  
  sal number(8,2) default 0,
  gender varchar2(10),
  title varchar2(20),
  POSITION  varchar2(20),
  department varchar2 (40) not null,
  constraint teach_ch_gender check ( gender in ('男','女')),
  constraint teach_ch_title check ( title in ('三级教师','二级教师','一级教师','高级教师','特级教师')),
  primary key(teacher_id,teacher_name) 
);
--建数据
insert  into teachers values(10001001,'陈思宇',46,15689.00,'男','特级教师','校长','校长室');
insert  into teachers values(10001002,'文强',44,14971.00,'男','特级教师','副校长','校长室');
insert  into teachers values(10001003,'吴玲',41,14571.00,'女','高级教师','办公室主任','办公室');
insert  into teachers values(10001004,'章丽',41,14121.00,'女','高级教师','教务处主任','教务处');
insert  into teachers values(10001005,'张志东',41,14121.00,'男','高级教师','财务处主任','财务处');
insert  into teachers values(10001006,'熊浩宇',49,14178.00,'女','一级教师','招生办主任','招生办');
insert  into teachers values(10001007,'朱雯',49,12008.00,'女','一级教师','招生办助理','招生办');
insert  into teachers values(10001008,'张志强',49,11982.00,'女','一级教师','财务处助理','财务处');
insert  into teachers values(10001009,'朱国斌',49,10987.00,'男','二级教师','财务处助理','财务处');

--查看表
test=# select * from teachers;
test-# /

 teacher_id | teacher_name | age |  sal  | gender | title  | position | department
------------+--------------+-----+----------+--------+----------+------------+------------
  10001001 | 陈思宇    | 46 | 15689.00 | 男   | 特级教师 | 校长    | 校长室
  10001002 | 文强     | 44 | 29942.00 | 男   | 特级教师 | 副校长   | 校长室
  10001003 | 吴玲     | 41 | 29142.00 | 女   | 高级教师 | 办公室主任 | 办公室
  10001004 | 章丽     | 41 | 28242.00 | 女   | 高级教师 | 教务处主任 | 教务处
  10001005 | 张志东    | 41 | 28242.00 | 男   | 高级教师 | 财务处主任 | 财务处
  10001006 | 熊浩宇    | 49 | 28356.00 | 女   | 一级教师 | 招生办主任 | 招生办
  10001007 | 朱雯     | 49 | 24016.00 | 女   | 一级教师 | 招生办助理 | 招生办
  10001008 | 张志强    | 49 | 23964.00 | 女   | 一级教师 | 财务处助理 | 财务处
  10001009 | 朱国斌    | 49 | 21974.00 | 男   | 二级教师 | 财务处助理 | 财务处

(9 rows)


--测试
test=# select age,sumc2(title) from teachers group by age;
test-# /
 age |       sumc2
-----+-------------------
  49 | 一级教师,二级教师
  44 | 特级教师
  46 | 特级教师
  41 | 高级教师
(4 rows)

Time: 1.517 ms

创建一个新的测试表,再次测试

drop table tt02;

create table tt02 (
  id int,
  name varchar2(200)
);

--kes:
declare 
  v int;
begin
   for i in 1..100000 loop
        select ceil(random()*10000 + 1) into v from dual;
        insert into tt02 values(v,'特级教师');
        select ceil(random()*10000 + 1) into v from dual;
        insert into tt02 values(v,'高级教师');
   end loop;
end;

--oracle:
declare 
  v int;
begin
  for i in 1..1000 loop
      select dbms_random.value(0,100000) into v from dual;
      insert into tt02 values(v,'特级教师');
      select dbms_random.value(0,100000) into v from dual;
      insert into tt02 values(v,'高级教师');
  end loop;
end;

test=# select * from tt02;

 id |  name
----+----------
 1 | 特级教师
 2 |
 3 | 特级教师
 4 | 中级教师
 5 | 特级教师
 6 |
 7 | 特级教师
 8 | 中级教师
(8 rows)

test=# select sumc2(name) from tt02;
test-# /

    sumc2
-------------------
 特级教师,中级教师

(1 row)

故此测试结果和oracle一致,符合基本的预期

注意:以上代码内容未对并行条件下进行考虑,后期将对齐进行讲解