`
linwei_211
  • 浏览: 188484 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

自定义聚合函数以及字符串连接超长的解决

阅读更多

Oracle的定制功能十分强大。前几天在碰到一个问题的时候,查询文档发现,oracle不但允许用户定制自己的函数,还可以定制自己的聚集函数和分析函数。

下面是我使用自定义聚集函数建立一个字符串“sum”的小例子。

类型声明:
CREATE OR REPLACE TYPE SUM_LINK AS OBJECT (
  STR VARCHAR2(30000),
  STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT SUM_LINK) RETURN NUMBER,
  MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT SUM_LINK, VALUE IN VARCHAR2) RETURN NUMBER,
  MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN SUM_LINK, RETURNVALUE OUT VARCHAR2, FLAGS IN NUMBER) RETURN NUMBER,
  MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT SUM_LINK, CTX2 IN SUM_LINK) RETURN NUMBER
  )

类型主体:
 
CREATE OR REPLACE TYPE BODY SUM_LINK IS
  STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT SUM_LINK) RETURN NUMBER IS
  BEGIN
  SCTX := SUM_LINK(NULL);
  RETURN ODCICONST.SUCCESS;
  END;

  MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT SUM_LINK, VALUE IN VARCHAR2) RETURN NUMBER IS
  BEGIN
 SELF.STR := SELF.STR || VALUE||';';
 RETURN ODCICONST.SUCCESS;
 END;

 MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN SUM_LINK, RETURNVALUE OUT VARCHAR2, FLAGS IN NUMBER) RETURN NUMBER IS
 BEGIN
  RETURNVALUE := SELF.STR;
 RETURN ODCICONST.SUCCESS;
 END;

 MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT SUM_LINK, CTX2 IN SUM_LINK) RETURN NUMBER IS
 BEGIN
  NULL;
  RETURN ODCICONST.SUCCESS;
  END;
  END;
 
 
方法创建:
 
CREATE OR REPLACE FUNCTION SUM_LINK(P_STR VARCHAR2) RETURN VARCHAR2
  AGGREGATE USING SUM_LINK;

 

  建表、插入数据,用来测试:
  
CREATE TABLE TEST (ID NUMBER, NAME VARCHAR2(20));
   INSERT INTO TEST VALUES (1, 'AAA');
   INSERT INTO TEST VALUES (2, 'BBB');
   INSERT INTO TEST VALUES (1, 'ABC');
   INSERT INTO TEST VALUES (3, 'CCC');
   INSERT INTO TEST VALUES (2, 'DDD');
   COMMIT;

 

测试自定义函数和类型:
SQL> SELECT ID, SUM_LINK(NAME) NAME FROM TEST GROUP BY ID;

        ID NAME
---------- ----------------------------

         1      AAA;ABC;

         2      BBB;DDD;

         3      CCC;
 
这里介绍一下通过SQL的方法来解决同样的问题。

对于下面这个利用自定义聚集函数的例子,将其改写为直接用SQL实现:
SQL> SELECT * FROM TAB;
TNAME  TABTYPE  CLUSTERID
------ -------- ------------------------------------ ------- ----------
SY_NAME SYNONYM
T      TABLE
TEST   TABLE
TEST1  TABLE
SUM_BLOB TABLE
SUM_CLOB TABLE
V_T    VIEW
V_TEST VIEW
已选择8行。
SQL> SELECT TABTYPE, SUM_LINK(TNAME) TNAME FROM TAB GROUP BY TABTYPE;
TABTYPE TNAME
------- --------------------------------------------------
SYNONYM SY_NAME
TABLE T,TEST1,SUM_CLOB,SUM_BLOB,TEST
VIEW V_T,V_TEST

    除了利用自定义聚集函数外,SQL函数中能将多个字符串合并在一起的只有SYS_CONNECSUM_BY_PATH了。
而这个函数只能应用在树型查询中,为了能使用这个函数,必须人为的构造出树来。
    也就是说,必须可以构造出一个CONNECT BY列使得相同的TABTYPE的TNAME可以用SYS_CONNECSUM_BY_PATH连接起来。
连接列可以使用ROW_NUMBER() OVER()来构造,在CONNECT BY的时候指定当前列的等于父列的值加1。
并在START WITH时指定起始值为1。
    最后对TABTYPE进行分组,取得最大值就是最终需要的结果

SQL> SELECT TABTYPE, MAX(LTRIM(SYS_CONNECSUM_BY_PATH(TNAME, ','), ',')) TNAME
2 FROM
3 (
4 SELECT TABTYPE, TNAME, ROW_NUMBER() OVER(PARTITION BY TABTYPE ORDER BY TNAME) RN
5 FROM TAB
6 )
7 START WITH RN = 1
8 CONNECT BY PRIOR RN + 1 = RN
9 AND PRIOR TABTYPE = TABTYPE
10 GROUP BY TABTYPE;
TABTYPE TNAME
------- --------------------------------------------------
SYNONYM SY_NAME
TABLE T,TEST,TEST1,SUM_BLOB,SUM_CLOB
VIEW V_T,V_TEST


      不过上面两种方法都会面临一个问题,就是如果聚集连接的字符串长度如果超过了VARCHAR2类型所允许的最大长度,就会导致字符串超长的错误。


自定义聚集函数这里就不重复了,可以参考上面的介绍:
SQL> SELECT SUM_LINK(TNAME) FROM TAB;
SUM_LINK(TNAME)
-------------------------------------------------------------------------------------------
BAK_SHGOV_ORDER,BAK_SHGOV_ORDER_BAK,PLAN_TABLE,SHGOV_ORDER,SHGOV_ORDER_BAK,T,T1,TEST,SUM_SQL
SQL> SELECT SUM_LINK(SEQUENCE_NAME) FROM ALL_SEQUENCES;
SELECT SUM_LINK(SEQUENCE_NAME) FROM ALL_SEQUENCES
*
ERROR 位于第 1 行:
ORA-06502: PL/SQL: 数字或值错误 : 字符串缓冲区太小
ORA-06512: 在"TEST.SUM_LINK", line 16
ORA-06512: 在line 1
SQL> SELECT MAX(LTRIM(SYS_CONNECSUM_BY_PATH(SEQUENCE_NAME, ','), ',')) NAME
2 FROM
3 (
4 SELECT SEQUENCE_NAME, ROW_NUMBER() OVER(ORDER BY SEQUENCE_NAME) RN
5 FROM ALL_SEQUENCES
6 )
7 START WITH RN = 1
8 CONNECT BY PRIOR RN + 1 = RN
9 ;
FROM ALL_SEQUENCES
*
ERROR 位于第 5 行:
ORA-01489: 字符串连接的结果过长
显然是由于要连接的字符串太长了,导致Oracle的字符串处理过程中出现了错误。
上面的两种方法都没有办法避免这个问题。
      但是ALL_SEQUENCES中的记录只有几百个,每个名称的长度不会超过30,因此最终的长度不会超过32767。
      根据Oracle给出的错误信息,显然是在处理输出参数RETURNVALUE的时候是安装SQL类型的VARCHAR2长度4000做的限制,那么只需要修改输出参数和聚集函数的返回值类型为CLOB类型即可
SQL> CREATE OR REPLACE TYPE SUM_LINK AS OBJECT (
2 STR VARCHAR2(32767),
3 STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT SUM_LINK) RETURN NUMBER,
4 MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT SUM_LINK, VALUE IN VARCHAR2) RETURN NUMBER,
5 MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN SUM_LINK, RETURNVALUE OUT CLOB, FLAGS IN NUMBER) RETURN NUMBER,
6 MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT SUM_LINK, CTX2 IN SUM_LINK) RETURN NUMBER
7 )
8 /
类型已创建。


SQL> CREATE OR REPLACE TYPE BODY SUM_LINK IS
2 STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT SUM_LINK) RETURN NUMBER IS
3 BEGIN
4 SCTX := SUM_LINK(NULL);
5 RETURN ODCICONST.SUCCESS;
6 END;
7
8 MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT SUM_LINK, VALUE IN VARCHAR2) RETURN NUMBER IS
9 BEGIN
10 SELF.STR := SELF.STR || VALUE || ',';
11 RETURN ODCICONST.SUCCESS;
12 END;
13
14 MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN SUM_LINK, RETURNVALUE OUT CLOB, FLAGS IN NUMBER) RETURN NUMBER IS
15 BEGIN
16 RETURNVALUE := SUBSTR(SELF.STR, 1, LENGTH(SELF.STR) - 1);
17 RETURN ODCICONST.SUCCESS;
18 END;
19
20 MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT SUM_LINK, CTX2 IN SUM_LINK) RETURN NUMBER IS
21 BEGIN
22 NULL;
23 RETURN ODCICONST.SUCCESS;
24 END;
25 END;
26 /
类型主体已创建。

 

SQL> CREATE OR REPLACE FUNCTION SUM_LINK(P_STR VARCHAR2) RETURN CLOB
2 AGGREGATE USING SUM_LINK;
3 /
函数已创建。


SQL> SELECT SUM_LINK(SEQUENCE_NAME) FROM ALL_SEQUENCES;
这个检索结果截图我就不粘了 ,我的库里SEQUENCE太多了.......

 

总结一下:
  ORA-06502: PL/SQL: 数字或值错误 : 字符串缓冲区太小
  引起这种问题的根源就是存放数据的缓冲区长度比要存的数据本身的长度小,造成的,那么知道问题的原因,解决起来也就容易多了。
 
  可以参看一下
http://www.blogjava.net/wangbing/archive/2010/03/15/315482.html这篇文章中关于关于oracle中varchar2的最大长度的介绍。
 
观点是:varchar2有两个最大长度:一个是在字段类型4000;一个是在PL/SQL中变量类型32767。
  也就是说schema级varchar2的长度限制都是4000,而在PL/SQL代码级的长度限制是32767。这是一个比较容易出错的地方,也很容易被忽略。
 
因为在函数中我可以声明长度超过4000的字符串变量,并且将它作为返回值,这里是不会提示编译错误的。
  这个函数平时都可以正常执行,而且网络上的聚合函数的例子也大多都是上面那么写的,是由于作者没有考虑大数据量的问题。一旦用到项目上,遭遇大数据量,这个字符串长度超过4000,函数执行就会出错。证明这一点极容易被忽略。

  再列举一个例子:
  -- 准备自定义类型 strcat_type
 create type strcat_type as object (
    cat_string varchar2(4000),
    static function ODCIAggregateInitialize(cs_ctx In Out strcat_type) return number,
    member function ODCIAggregateIterate(self In Out strcat_type,value in varchar2) return number,
    member function ODCIAggregateMerge(self In Out strcat_type,ctx2 In Out strcat_type) return number,
    member function ODCIAggregateTerminate(self In Out strcat_type,returnValue Out varchar2,flags in number) return number
)

-- 准备自定义类型体
create type body strcat_type is
  static function ODCIAggregateInitialize(cs_ctx IN OUT strcat_type) return number
  is
  begin
      cs_ctx := strcat_type( null );
      return ODCIConst.Success;
  end;

  member function ODCIAggregateIterate(self IN OUT strcat_type,
                                       value IN varchar2 )
  return number
  is
  begin
      self.cat_string := self.cat_string || value || ';';
      return ODCIConst.Success;
  end;

  member function ODCIAggregateTerminate(self IN Out strcat_type,
                                         returnValue OUT varchar2,
                                         flags IN number)
  return number
  is
  begin
      returnValue := ltrim(rtrim(self.cat_string,','),',');
      return ODCIConst.Success;
  end;

  member function ODCIAggregateMerge(self IN OUT strcat_type,
                                     ctx2 IN Out strcat_type)
  return number
  is
  begin
      self.cat_string := self.cat_string || ',' || ctx2.cat_string;
      return ODCIConst.Success;
  end;

end;
  -- 创建字符串求和自定义函数
 CREATE or replace FUNCTION sum_str(input varchar2 )
 RETURN varchar2
 PARALLEL_ENABLE AGGREGATE USING strcat_type;
 -- 表和数据准备我就不详细介绍了.....

 -- 测试一下自己的方法
SQL> select sum_str(decode(id, 1, name, null)) a,
  2         sum_str(decode(id, 2, name, null)) b,
  3         sum_str(decode(id, 3, name, null)) c
  4    from test
  5  ;

A   B   C
--------  -------------  --------------
AAA;ABC;  BBB;DDD;       CCC;

这是数据少的情况下。不会报错,一但数据量过大,就会报
ORA-06502: PL/SQL: 数字或值错误 : 字符串缓冲区太小

修改一下数据类型;如下:
--修改函数返回类型为CLOB
CREATE OR REPLACE FUNCTION sum_str(input varchar2 ) RETURN
   CLOB PARALLEL_ENABLE AGGREGATE USING strcat_type;
--将类型声明中的varchar2(4000)改为varchar2(32767)
CREATE OR REPLACE TYPE "STRCAT_TYPE"                                                                                                                                                                                                                            as object ( cat_string
   varchar2(32767),
      static function ODCIAggregateInitialize(cs_ctx In Out strcat_type) return number,
      member function ODCIAggregateIterate(self In Out strcat_type,value in varchar2) return number,
      member function ODCIAggregateMerge(self In Out strcat_type,ctx2 In Out strcat_type) return number,
      member function ODCIAggregateTerminate(self In Out strcat_type,returnValue Out clob,flags in number) return number
      )
--修改类型体的输出参数
create or replace type body strcat_type is static function
   ODCIAggregateInitialize(cs_ctx IN OUT strcat_type) return
   number is begin cs_ctx := strcat_type( null ); return
   ODCIConst.Success; end;

   member function ODCIAggregateIterate(self IN OUT
   strcat_type, value IN varchar2) return number is begin
   self.cat_string := self.cat_string || value || ';'; return
   ODCIConst.Success; end;

   member function ODCIAggregateTerminate(self IN Out
   strcat_type, returnValue OUT clob, flags IN number)
   return number is begin returnValue :=
   ltrim(rtrim(self.cat_string,','),','); return
   ODCIConst.Success; end;

   member function ODCIAggregateMerge(self IN OUT strcat_type,
   ctx2 IN Out strcat_type) return number is begin
   self.cat_string := self.cat_string || ',' ||
   ctx2.cat_string; return ODCIConst.Success; end;

   end;


再次使用sum_str方法,执行sql语句的时候不会再有任何问题了.......

 

分享到:
评论

相关推荐

    Oracle10g自定义聚合函数(字符串拼接)

    * 自定义聚合函数 wmsys.wm_concat 替换办法 * 超大字符串拼接,单个字符串4000、分隔符100,可拼出超4000的超长字符串 * 可自定义指定分隔符separator * 可自定义指定排序字段sequence,对于数字或日期类型的...

    oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串

    varchar2 1~4000字节 可变长度字符串,与CHAR类型相比,使用VARCHAR2可以节省磁盘空间,但查询效率没有char类型高 数值类型 Number(m,n) m(1~38) n(-84~127) 可以存储正数、负数、零、定点数和精度为38位的浮点数...

    Oracle课件.pdf

    5.3字符串连接操作符(||) 6. 高级查询 6.1 DISTINCT操作 6.2 NULL操作 6.3 IN 操作 6.4 BETWEEN…AND… 6.5 LIKE模糊查询 6.6 集合运算 6.7 连接查询 第3章 子查询和常用函数 1. 子查询 2. Oracle中的...

    关系型数据库管理系统之MySql学习总结

    文章目录数据库介绍MySql介绍添加数据导入数据查询数据内连接左外连接/右外连接子查询约束函数聚合函数分组函数数学函数字符串函数日期时间函数条件判断函数系统信息函数加密函数格式化函数自定义函数视图事务存储...

    XQuery权威指南(简码·扫描版)

     17.6 字符串连接和拆分  17.7 操作字符串  17.8 空白符和字符串  17.9 国际化考虑 第18章 正则式  18.1 正则式的结构  18.2 表示单个字符  18.3 表示任意字符  18.4 表示字符的组  18.5 字符类表达式  ...

    C#开发经验技巧宝典

    0909 如何对字符串进行查询 532 0910 如何进行单条数据的添加 533 0911 如何进行批量数据的添加 533 0912 如何对数据进行修改 534 0913 如何对数据进行删除 534 0914 对数据库数据进行局部删除 534 ...

    Python Cookbook

    1.3 测试一个对象是否是类字符串 8 1.4 字符串对齐 10 1.5 去除字符串两端的空格 11 1.6 合并字符串 11 1.7 将字符串逐字符或逐词反转 14 1.8 检查字符串中是否包含某字符集合中的字符 15 1.9 简化字符串的...

    C#编程经验技巧宝典

    76 <br>0111 计算字符串中子字符串出现的次数 76 <br>0112 获得字符串中大写字母的个数 77 <br>0113 获得某字符在字符串中最后出现的位置 78 <br>0114 如何找出字符串中某一字符的所有位置 78...

    明日科技C#开发入门及项目实战

    实例035 从字符串中分离文件路径、文件名及扩展名 实例036 对字符串进行加密与解密 实例037 开发一个进制转换器 实例038 将字符串的每个字符进行颠倒输出 实例039 根据标点符号对字符串进行分行 实例040 将汉字转换...

    MySQL 5.1中文手冊

    12.3.1. 字符串比较函数 12.4. 数值函数 12.4.1. 算术操作符 12.4.2. 数学函数 12.5. 日期和时间函数 12.6. MySQL使用什么日历? 12.7. 全文搜索功能 12.7.1. 布尔全文搜索 12.7.2. 全文搜索带查询扩展 12.7.3. 全文...

    mysql官方中文参考手册

    12.3.1. 字符串比较函数 12.4. 数值函数 12.4.1. 算术操作符 12.4.2. 数学函数 12.5. 日期和时间函数 12.6. MySQL使用什么日历? 12.7. 全文搜索功能 12.7.1. 布尔全文搜索 12.7.2. 全文搜索带查询扩展 12.7.3. 全文...

    MYSQL中文手册

    12.3.1. 字符串比较函数 12.4. 数值函数 12.4.1. 算术操作符 12.4.2. 数学函数 12.5. 日期和时间函数 12.6. MySQL使用什么日历? 12.7. 全文搜索功能 12.7.1. 布尔全文搜索 12.7.2. 全文搜索带查询扩展 ...

    MySQL 5.1参考手册中文版

    12.3.1. 字符串比较函数 12.4. 数值函数 12.4.1. 算术操作符 12.4.2. 数学函数 12.5. 日期和时间函数 12.6. MySQL使用什么日历? 12.7. 全文搜索功能 12.7.1. 布尔全文搜索 12.7.2. 全文搜索带查询扩展 12.7....

    MySQL 5.1参考手册

    12.3.1. 字符串比较函数 12.4. 数值函数 12.4.1. 算术操作符 12.4.2. 数学函数 12.5. 日期和时间函数 12.6. MySQL使用什么日历? 12.7. 全文搜索功能 12.7.1. 布尔全文搜索 12.7.2. 全文搜索带查询扩展 12.7.3. 全文...

    MySQL 5.1参考手册 (中文版)

    12.3.1. 字符串比较函数 12.4. 数值函数 12.4.1. 算术操作符 12.4.2. 数学函数 12.5. 日期和时间函数 12.6. MySQL使用什么日历? 12.7. 全文搜索功能 12.7.1. 布尔全文搜索 12.7.2. 全文搜索带查询扩展 12.7.3. 全文...

Global site tag (gtag.js) - Google Analytics