Oracle 自定义函数实现split功能,支持超长字符串和clob类型的分隔

本文介绍了一种在Oracle数据库中高效拆分超长字符串的方法,通过自定义函数和使用clob类型,解决了传统正则表达式方法在处理长字符串时的效率和长度限制问题。

一年又快过去了,,,,12月,广州的12月,一天冷,一天热,一周内体验四季!

 

先分享一个,oracle中比较常用的,根据分隔符拆分字符串为多行结果集的sql写法,平时对于不是特别长的字符串的拆分,用着还是挺方便的。代码及查询的效果如下:

select regexp_substr('abc,def,ghi,jkl', '[^,]+', 1, level) c1
  from dual
connect by level <= regexp_count('abc,def,ghi,jkl', '[^,]+')

然后,前些日子帮朋友调试一个存储过程的时候,发现传了个贼长的字符串进来,用上面的正则 + connect by的方法处理起来就比较慢,而且也不支持超长字符串的拆分,百度折腾了下,决定写个函数处理。

一、解决思路

1、利用管道函数pipelined和自定义的table类型,通过函数拆分字符串并插入到结果集中,直接返回拆分后的结果集。

2、然后,通过clob类型传入,解决超长字符串的问题,在函数中分段进行截取,拆分。

二、代码实现

1、先建一个自定义的table类型,数据类型为varchar2

CREATE OR REPLACE TYPE type_table_varchar2 IS TABLE OF VARCHAR(4000);

2、函数处理

create or replace function f_split(as_clob clob, delimiter varchar2) return type_table_varchar2
   pipelined is
   ls_str1 varchar2(8000);
   ln_cnt  number(8);
   ls_str2 varchar2(2000);
   x       number(8);
   y       number(8);
begin
   -- 算一下要循环几次
   ln_cnt := ceil(length(as_clob) / 4000);
   -- 开始循环获取
   for i in 1 .. ln_cnt loop
      -- 取出4000个字符
      ls_str1 := to_char(substr(as_clob, (4000 * (i - 1)) + 1, 4000));
      -- 定位到1
      x := 1;
      y := 1;
      -- 开始循环截取
      loop
         -- 定位分隔符
         x := instr(ls_str1, delimiter, y);
         -- 如果存在分隔符,那就从现在的位置截取到分隔符前一位
         if x > 0 then
            -- 截取并去空格
            ls_str2 := ls_str2 || trim(substr(ls_str1, y, x - y));
            -- 如果长度大于0,那就是有效的字符串
            if ls_str2 is not null then
               -- 取出的单条字符串插入集合,并重置ls_str2为空
               pipe row(ls_str2);
               ls_str2 := '';
            end if;
            -- 查找开始位置往当前定位后挪1位
            y := x + 1;
         else
            -- 当前位置往后面不存在分隔符的话,取出后面的字符串,退出本次循环
            ls_str2 := ls_str2 || trim(substr(ls_str1, y, 4000 - y + 1));
            -- dbms_output.put_line('后面没分隔符了,取出:' || ls_str2);
            exit;
         end if;
      end loop;
   end loop;
   -- 最后的剩余部分处理
   if ls_str2 is not null then
      pipe row(ls_str2);
   end if;
   return;
end f_split;

三、测试

1、使用方法就很简单啦,RT

因为有进行前后去空格和空字符串不插入处理,如果不需要这个细节的自行修改咯。

2、试下超长字符串,一个差不多14400长的clob,搞它一搞

拆分和插入大概用了0.05s,效率还是挺不错的。

 

-------------------------------------------------------------------------------------------

2020年3月14,

修改了上面的:ls_str2 := ls_str2 || trim(substr(ls_str1, y, 4000 - y + 1));

另外,因修改另外一个程序,涉及到,拆分后的每一个字符串,有可能超过4000,所以,加上一个处理成clob的方法。

类型定义:CREATE OR REPLACE  type type_table_clob IS TABLE OF CLOB;

函数:

  /**
   * 拆分字符串,返回table(clob)
   * 参数:
   * as_clob      要拆分的字符串
   * as_delimiter 分隔符
   */
  function f_split(as_clob clob, as_delimiter varchar2) return type_table_clob
     pipelined is
     ls_str1 varchar2(8000);
     ln_cnt  number(8);
     ls_str2 clob;
     x       number(8);
     y       number(8);
  begin
     -- 算一下要循环几次
     ln_cnt := ceil(length(as_clob) / 4000);
     -- 开始循环获取
     for i in 1 .. ln_cnt loop
        -- 取出4000个字符
        ls_str1 := to_char(substr(as_clob, (4000 * (i - 1)) + 1, 4000));
        -- dbms_output.put_line('开始一次->'||ls_str1);
        -- 定位到1
        x := 1;
        y := 1;
        -- 开始循环截取
        loop
           -- 定位分隔符
           x := instr(ls_str1, as_delimiter, y);
           -- 如果存在分隔符,那就从现在的位置截取到分隔符前一位
           if x > 0 then
              -- 截取并去空格
              ls_str2 := ls_str2 || trim(substr(ls_str1, y, x - y));
              -- 如果长度大于0,那就是有效的字符串
              if ls_str2 is not null then
                 -- 取出的单条字符串插入集合,并重置ls_str2为空
                 pipe row(ls_str2);
                 -- dbms_output.put_line(ls_str2);
                 ls_str2 := '';
              end if;
              -- 查找开始位置往当前定位后挪1位
              y := x + 1;
           else
              -- 当前位置往后面不存在分隔符的话,取出后面的字符串,退出本次循环
              ls_str2 := ls_str2 || trim(substr(ls_str1, y, 4000 - y + 1));
              -- dbms_output.put_line('后面没分隔符了,取出:' || ls_str2);
              exit;
           end if;
        end loop;
        -- dbms_output.put_line('结束一次->'||ls_str2);
     end loop;
     -- 最后的剩余部分处理
     if ls_str2 is not null then
        pipe row(ls_str2);
        --dbms_output.put_line(ls_str2);
     end if;
     return;
  end f_split;

 

### Oracle 数据库中的字符串分割方法 在 Oracle 数据库中,可以使用多种方式来实现字符串的分割功能。以下是几种常见的方法: #### 1. 使用自定义类型函数 可以通过创建用户定义的数据类型以及相应的表函数来进行字符串分割。例如,在引用材料中提到的一种方法是通过 `TYPE` `FUNCTION` 的组合完成此操作。 首先,定义一个集合类型的对象: ```sql CREATE OR REPLACE TYPE STR_SPLIT AS TABLE OF VARCHAR2(4000); ``` 接着编写一个 PL/SQL 函数用于返回分割后的结果集[^1]: ```sql CREATE OR REPLACE FUNCTION F_SPLIT_STRING ( var_str IN VARCHAR2, var_split IN VARCHAR2 ) RETURN STR_SPLIT PIPELINED IS idx NUMBER; str_temp VARCHAR2(4000); BEGIN LOOP idx := INSTR(var_str, var_split); IF idx > 0 THEN str_temp := SUBSTR(var_str, 1, idx - 1); var_str := LTRIM(SUBSTR(var_str, idx + LENGTH(var_split))); ELSE str_temp := var_str; var_str := NULL; END IF; EXIT WHEN str_temp IS NULL; PIPE ROW(str_temp); END LOOP; END; / ``` 调用该函数时可直接查询其结果作为表格形式展示[^3]: ```sql SELECT COLUMN_VALUE FROM TABLE(F_SPLIT_STRING('A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z', ',')); ``` #### 2. 利用正则表达式 REGEXP_SUBSTR 进行复杂模式匹配 对于更复杂的分隔符或者需要支持多级嵌套的情况,则推荐采用正则表达式的解决方案。REGEXP_SUBSTR 是一种强大的工具,它允许指定任意数量级别的捕获组并提取相应子串[^2]。 假设有一个逗号分隔列表 `'apple,banana,cherry'`, 可以这样写 SQL 来获取每一个水果名称: ```sql WITH DATA_SET (ID, STRING_VAL) AS ( SELECT 1, 'apple,banana,cherry' FROM DUAL UNION ALL SELECT 2, 'dog/cat/bird/fish' FROM DUAL -- Example with different delimiter '/' ) SELECT ID, LEVEL NUM, TRIM(REGEXP_SUBSTR(STRING_VAL,'[^'||CASE WHEN INSTR(ID,',')>0 THEN ',' ELSE '/' END||']+',LEVEL)) ITEM FROM DATA_SET CONNECT BY PRIOR ID=ID AND PRIOR SYS_GUID() IS NOT NULL ORDER SIBLINGS BY LEVEL ASC ; ``` #### 3. 基于标准管道化行生成器 PIPELINE 实现灵活处理逻辑 如果希望进一步增强灵活性,还可以基于 pipeline 行生成机制来自由控制每一行数据如何被生产出来。这种方法尤其适合那些涉及循环迭代或其他动态计算场景下的应用案例[^5]: 示例代码如下所示: ```plsql CREATE OR REPLACE PACKAGE PKG_TABLESPLIT AS TYPE OBJ_TABLESPLIT IS RECORD(NUMBER_INDEX PLS_INTEGER, VALUE CLOB); TYPE TBL_TABLESPLIT IS TABLE OF OBJ_TABLESPLIT INDEX BY BINARY_INTEGER; FUNCTION SPLIT(P_LIST IN OUT NOCOPY NCLOB ,P_SEP CHAR DEFAULT CHR(9))RETURN TBL_TABLESPLIT PIPELINED; END PKG_TABLESPLIT; / CREATE OR REPLACE PACKAGE BODY PKG_TABLESPLIT AS FUNCTION SPLIT(P_LIST IN OUT NOCOPY NCLOB ,P_SEP CHAR DEFAULT CHR(9)) RETURN TBL_TABLESPLIT PIPELINED IS V_NUM INTEGER:=0; L_IDX INT; BEGIN WHILE LENGTHB(P_LIST)>0 LOOP L_IDX :=INSTR(P_LIST,P_SEP); IF(L_IDX>0)THEN PIPE ROW(OBJ_TABLESPLIT(V_NUM,SUBSTR(P_LIST,1,(L_IDX)-1))); P_LIST :=SUBSTR(P_LIST,(L_IDX)+LENGTH(P_SEP)); ELSE PIPE ROW(OBJ_TABLESPLIT(V_NUM,P_LIST)); EXIT; END IF; V_NUM :=V_NUM+1; END LOOP; RETURN; END SPLIT; END PKG_TABLESPLIT; / ``` 以上介绍了三种主要的方式可以在 Oracle 中执行字符串拆分任务。每种方案都有各自的特点适用范围,请根据实际业务需求选择合适的解决办法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值