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;

 

<think>我们已知用户的问题:在Oracle中使用for循环处理多个字符串的方法。参考了站内引用中的相关部分,特别是引用[2]引用[5]中关于字符串拼接的内容,以及引用[4]中关于字符串分割的内容。用户希望使用for循环来处理多个字符串,可能包括字符串的拼接、分割等操作。 结合引用内容,我们可以提供以下思路: 1. 使用游标FOR循环从数据库中读取多个字符串,然后进行处理(如拼接)。 2. 使用数值FOR循环遍历一个字符串集合(例如数组或分割后的字符串片段)进行处理。 具体方法如下: ### 1. 使用游标FOR循环处理多个字符串(例如拼接) 在Oracle存储过程中,可以使用游标FOR循环来遍历表中的多行字符串数据,并将它们拼接成一个字符串。 **示例:** 将某个字段的多行值拼接成一个字符串(用逗号分隔)。 假设有一张表`tbl_strings`,其中有一个字段`str_value`存储字符串。我们使用游标FOR循环遍历所有行,并使用字符串连接操作符`||`进行拼接。 ```sql DECLARE v_result VARCHAR2(4000); -- 存储拼接结果 BEGIN v_result := ''; -- 初始化 FOR rec IN (SELECT str_value FROM tbl_strings) LOOP -- 如果是第一个值,则不加逗号,否则加逗号分隔 IF v_result IS NULL THEN v_result := rec.str_value; ELSE v_result := v_result || ',' || rec.str_value; END IF; END LOOP; DBMS_OUTPUT.PUT_LINE('拼接结果: ' || v_result); END; / ``` 注意:在Oracle 11g及以上,可以使用`LISTAGG`函数代替循环拼接,但在某些复杂场景或需要逐行处理时,循环仍然有用。 ### 2. 使用数值FOR循环处理字符串数组(需先将字符串分割成数组) 如果有一个字符串,需要先分割成多个子字符串,然后使用数值FOR循环逐个处理,我们可以引用[4]中的分割函数。 **步骤:** 1. 创建一个函数字符串按照分隔拆分成多行(如引用[4]所示)。 2. 在存储过程中使用数值FOR循环遍历拆分后的每一行。 **示例:** 分割字符串并循环处理每个子串。 首先,创建分割函数(基于引用[4]修改为返回数组或使用管道函数): ```sql -- 创建分割函数(管道函数) CREATE OR REPLACE FUNCTION split_str( p_string IN VARCHAR2, p_delimiter IN VARCHAR2 DEFAULT ',' ) RETURN SYS.ODCIVARCHAR2LIST PIPELINED AS v_start_pos NUMBER := 1; v_end_pos NUMBER; BEGIN WHILE v_start_pos <= LENGTH(p_string) LOOP v_end_pos := INSTR(p_string, p_delimiter, v_start_pos); IF v_end_pos = 0 THEN PIPE ROW (SUBSTR(p_string, v_start_pos)); EXIT; ELSE PIPE ROW (SUBSTR(p_string, v_start_pos, v_end_pos - v_start_pos)); v_start_pos := v_end_pos + LENGTH(p_delimiter); END IF; END LOOP; RETURN; END; / ``` 然后在存储过程中使用数值FOR循环处理每个子串: ```sql DECLARE v_input_str VARCHAR2(100) := 'apple,banana,orange'; v_delimiter VARCHAR2(1) := ','; BEGIN -- 使用数值FOR循环遍历分割后的子串 -- 注意:这里通过SELECT COUNT(*)获取分割后的元素个数,然后循环索引 -- 但更简单的方法是使用游标FOR循环直接遍历split_str函数的返回结果(推荐) -- 方法1:使用游标FOR循环(更简单) FOR item IN (SELECT COLUMN_VALUE AS item_str FROM TABLE(split_str(v_input_str, v_delimiter))) LOOP DBMS_OUTPUT.PUT_LINE('处理子串: ' || item.item_str); -- 这里可以添加针对每个子串的处理逻辑 END LOOP; -- 方法2:使用数值FOR循环(需要先知道元素个数) -- 首先获取元素个数 SELECT COUNT(*) INTO v_count FROM TABLE(split_str(v_input_str, v_delimiter)); -- 然后循环 FOR i IN 1..v_count LOOP -- 获取第i个元素(需要再次查询) SELECT COLUMN_VALUE INTO v_item FROM ( SELECT COLUMN_VALUE, ROWNUM rn FROM TABLE(split_str(v_input_str, v_delimiter)) ) WHERE rn = i; DBMS_OUTPUT.PUT_LINE('第'||i||'个子串: ' || v_item); END LOOP; END; / ``` 推荐使用方法1(游标FOR循环)直接遍历分割后的结果集,这样更高效。 ### 3. 在循环中处理多个字符串的其他方式 如果多个字符串不是来自表,而是来自一个数组(PL/SQL集合),则可以直接使用数值FOR循环遍历集合。 **示例:** 使用PL/SQL集合(数组)存储多个字符串,并用FOR循环处理。 ```sql DECLARE TYPE t_string_list IS TABLE OF VARCHAR2(100); v_strings t_string_list := t_string_list('one', 'two', 'three'); BEGIN -- 数值FOR循环从1到集合大小 FOR i IN 1..v_strings.COUNT LOOP DBMS_OUTPUT.PUT_LINE('字符串' || i || ': ' || v_strings(i)); END LOOP; END; / ``` ### 注意事项 - 字符串长度限制:在拼接时,注意`VARCHAR2`的最大长度(通常4000字节,在12c以上可设置为32767字节)。 - 性能:对于非常大的数据集,游标FOR循环可能较慢,可以考虑批量处理(BULK COLLECT)或使用内置函数(如`LISTAGG`)。 - 异常处理:在循环中添加异常处理以捕获可能的错误(如下标越界)。 ### 总结 在Oracle中使用FOR循环处理多个字符串的常用方法包括: 1. **游标FOR循环**:适用于从表中读取多行字符串进行处理(如拼接)[^2][^5]。 2. **数值FOR循环**:适用于遍历数组(集合)或已知数量的字符串序列,也可配合分割函数处理分割后的子串[^4]。 3. **结合分割函数**:将单个字符串分割成多个子串再用循环处理,推荐使用管道函数返回结果集并用游标FOR循环遍历[^4]。 以上方法可根据实际需求选择。 §§相关问题§§ 1. Oracle中如何将多行字符串合并为一个字符串? 2. Oracle中如何将字符串拆分成多个子串? 3. 在PL/SQL中,除了FOR循环,还有哪些方式可以遍历集合? </think>在Oracle中使用FOR循环处理多个字符串时,通常涉及以下几类场景:**字符串拼接**、**字符串分割****字符串遍历**。以下是具体方法示例,结合了PL/SQL的FOR循环字符串处理函数。 --- ### 一、字符串拼接:多行数据合并为单个字符串 **场景**:将查询结果的多行字符串拼接成单个字符串(如逗号分隔的列表)。 **方法**:使用游标FOR循环遍历查询结果,通过`||`操作符拼接字符串。 **示例代码**: ```sql DECLARE v_result VARCHAR2(4000); -- 存储拼接结果 BEGIN v_result := ''; -- 初始化 -- 游标FOR循环遍历员工姓名 FOR rec IN (SELECT employee_name FROM employees) LOOP IF v_result IS NULL THEN v_result := rec.employee_name; -- 第一个元素不加分隔符 ELSE v_result := v_result || ', ' || rec.employee_name; -- 后续元素加逗号分隔 END IF; END LOOP; DBMS_OUTPUT.PUT_LINE('拼接结果: ' || v_result); -- 输出: Name1, Name2, Name3 END; / ``` > **注意**:Oracle 11g+可使用`LISTAGG`函数简化(无需循环): > ```sql > SELECT LISTAGG(employee_name, ', ') WITHIN GROUP (ORDER BY employee_id) > FROM employees; > ``` --- ### 二、字符串分割:拆分字符串并循环处理子串 **场景**:将单个字符串分隔拆分成多个子串,并用FOR循环逐个处理。 **方法**: 1. 创建分割函数(管道函数) 2. 使用游标FOR循环遍历拆分结果 **步骤1:创建分割函数**(引用[4]的优化版本) ```sql CREATE OR REPLACE FUNCTION split_string( p_string IN VARCHAR2, p_delimiter IN VARCHAR2 DEFAULT ',' ) RETURN SYS.ODCIVARCHAR2LIST PIPELINED -- 返回集合类型 AS v_start NUMBER := 1; v_end NUMBER; BEGIN WHILE v_start <= LENGTH(p_string) LOOP v_end := INSTR(p_string, p_delimiter, v_start); -- 查找分隔符位置 EXIT WHEN v_end = 0; -- 无更多分隔符时退出 PIPE ROW(SUBSTR(p_string, v_start, v_end - v_start)); -- 输出子串 v_start := v_end + LENGTH(p_delimiter); -- 移动起始位置 END LOOP; PIPE ROW(SUBSTR(p_string, v_start)); -- 输出最后一个子串 RETURN; END; / ``` **步骤2:FOR循环处理分割后的子串** ```sql DECLARE v_input VARCHAR2(100) := 'apple,banana,orange'; BEGIN -- 游标FOR循环遍历分割后的子串 FOR item IN (SELECT COLUMN_VALUE AS fruit FROM TABLE(split_string(v_input))) LOOP DBMS_OUTPUT.PUT_LINE('处理: ' || item.fruit); -- 输出每个水果 -- 可在此添加自定义逻辑(如校验、转换等) END LOOP; END; / ``` **输出**: ``` 处理: apple 处理: banana 处理: orange ``` --- ### 三、字符串遍历:逐个字符处理 **场景**:遍历字符串中的每个字符进行操作(如字符替换、统计)。 **方法**:使用数值FOR循环配合`SUBSTR`函数。 **示例**:反转字符串 ```sql DECLARE v_str VARCHAR2(100) := 'Hello'; v_result VARCHAR2(100) := ''; BEGIN -- 倒序遍历每个字符 FOR i IN REVERSE 1..LENGTH(v_str) LOOP v_result := v_result || SUBSTR(v_str, i, 1); -- 拼接字符 END LOOP; DBMS_OUTPUT.PUT_LINE('反转结果: ' || v_result); -- 输出: olleH END; / ``` --- ### 四、动态SQL中的字符串循环处理 **场景**:根据字符串集合动态生成并执行SQL(如批量更新)。 **示例**:根据ID列表更新员工状态 ```sql DECLARE v_ids VARCHAR2(100) := '101,102,103'; -- ID列表 BEGIN FOR id_rec IN (SELECT COLUMN_VALUE AS emp_id FROM TABLE(split_string(v_ids))) LOOP EXECUTE IMMEDIATE ' UPDATE employees SET status = ''ACTIVE'' WHERE employee_id = :1' USING id_rec.emp_id; -- 动态绑定参数 END LOOP; COMMIT; END; / ``` --- ### 关键注意事项 1. **字符串长度限制**: - `VARCHAR2`默认最大4000字节,超出需用`CLOB`。 2. **性能优化**: - 大数据量时避免在循环内执行SQL(用`BULK COLLECT`代替)。 3. **空值处理**: - 拼接时用`NVL(rec.column, '')`防止`NULL`中断结果。 4. **分隔符兼容性**: - 分割函数需处理多字符分隔符(如`||`)。 > **替代方案参考**: > - 拼接用`LISTAGG` [^3] > - 分割用`JSON_TABLE` (Oracle 12c+) > - 动态SQL用`EXECUTE IMMEDIATE`绑定变量防注入
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值