【问题标题】:Is there a function to split a string in PL/SQL?PL/SQL 中有拆分字符串的函数吗?
【发布时间】:2011-04-12 06:06:52
【问题描述】:

我需要编写一个过程来规范化由一个字符连接的多个标记的记录。我需要获取这些标记拆分字符串并将每个标记作为新记录插入表中。 Oracle 有类似“拆分”功能的东西吗?

【问题讨论】:

标签: string oracle plsql split tokenize


【解决方案1】:

我需要一个拆分 clob 并确保该函数在 sql 中可用的函数。

create or replace type vchar_tab is table of varchar2(4000)
/
create or replace function split(
    p_list in clob,
    p_separator in varchar2 default '|'
) return vchar_tab pipelined is
    C_SQL_VCHAR_MAX constant integer:=4000;
    C_MAX_AMOUNT    constant integer:=28000;
    C_SEPARATOR_LEN constant integer:=length(p_separator);
    l_amount        integer:=C_MAX_AMOUNT;
    l_offset        integer:=1;
    l_buffer        varchar2(C_MAX_AMOUNT);
    l_list          varchar2(32767);
    l_index         integer;
begin
    if p_list is not null then
        loop
            l_index:=instr(l_list, p_separator);
            if l_index > C_SQL_VCHAR_MAX+1 then
                raise_application_error(-20000, 'item is too large for sql varchar2: len='||(l_index-1));
            elsif l_index > 0 then -- found an item, pipe it
                pipe row (substr(l_list, 1, l_index-1));
                l_list:=substr(l_list, l_index+C_SEPARATOR_LEN);
            elsif length(l_list) > C_SQL_VCHAR_MAX then
                raise_application_error(-20001, 'item is too large for sql varchar2: length exceeds '||length(l_list));
            elsif l_amount = C_MAX_AMOUNT then -- more to read from the clob
                dbms_lob.read(p_list, l_amount, l_offset, l_buffer);
                l_list:=l_list||l_buffer;
            else -- read through the whole clob
                if length(l_list) > 0 then
                    pipe row (l_list);
                end if;
                exit;
            end if;
        end loop;
    end if;

    return;
exception
    when no_data_needed then -- this happens when you don't fetch all records
        null;
end;
/

测试:

select *
from table(split('ASDF|IUYT|KJHG|ASYD'));

【讨论】:

    【解决方案2】:

    您可以使用 regexp_substr()。示例:

    create or replace type splitTable_Type is table of varchar2(100);
    
    declare
        l_split_table splitTable_Type;
    begin
      select
          regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level)
      bulk collect into
          l_split_table
      from dual
      connect by
          regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null;
    end;
    

    查询遍历逗号分隔的字符串,搜索逗号 (,),然后通过将逗号视为分隔符来拆分字符串。每当遇到分隔符时,它将字符串作为一行返回。

    level in statement regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level) 引用 Oracle 中的一个伪列,用于在层次查询中以数字格式标识层次级别:level in connect by

    【讨论】:

    • 我喜欢这个,因为 bulk collect into 而不是显式循环遍历正则表达式结果。由于 Oracle 没有为我们提供split() 实用程序,我们至少可以使用它提供的工具...
    【解决方案3】:

    apex_util.string_to_table - 请参阅my answer 这个question

    另外,在上述功能存在之前,我曾经发过一个解决方案here on my blog

    更新

    在APEX的更高版本中,apex_util.string_to_tabledeprecated,类似的功能apex_string.split是首选。

    【讨论】:

    • 感谢您的提示,遗憾的是,我无法使用该软件包,所以我最终使用了 redFilter 发布的功能。
    • apex 函数似乎不适用于 Oracle R12 Applications,因为我认为它默认不包含 Apex。
    • 抱歉,4 年后我才注意到我错误地将包名称输入为“apex_utility” - 它是“apex_util”(现已更正)。
    • 我不能在 Oracle 11g 上string_to_table,因为“Java 服务”被禁用。但是,SA 用户可以启用它们。
    【解决方案4】:

    你必须自己动手。例如,

    /* from :http://www.builderau.com.au/architect/database/soa/Create-functions-to-join-and-split-strings-in-Oracle/0,339024547,339129882,00.htm
    
    select split('foo,bar,zoo') from dual;
    select * from table(split('foo,bar,zoo'));
    
    pipelined function is SQL only (no PL/SQL !)
    */
    
    create or replace type split_tbl as table of varchar2(32767);
    /
    show errors
    
    create or replace function split
    (
        p_list varchar2,
        p_del varchar2 := ','
    ) return split_tbl pipelined
    is
        l_idx    pls_integer;
        l_list    varchar2(32767) := p_list;
        l_value    varchar2(32767);
    begin
        loop
            l_idx := instr(l_list,p_del);
            if l_idx > 0 then
                pipe row(substr(l_list,1,l_idx-1));
                l_list := substr(l_list,l_idx+length(p_del));
    
            else
                pipe row(l_list);
                exit;
            end if;
        end loop;
        return;
    end split;
    /
    show errors;
    
    /* An own implementation. */
    
    create or replace function split2(
      list in varchar2,
      delimiter in varchar2 default ','
    ) return split_tbl as
      splitted split_tbl := split_tbl();
      i pls_integer := 0;
      list_ varchar2(32767) := list;
    begin
      loop
        i := instr(list_, delimiter);
        if i > 0 then
          splitted.extend(1);
          splitted(splitted.last) := substr(list_, 1, i - 1);
          list_ := substr(list_, i + length(delimiter));
        else
          splitted.extend(1);
          splitted(splitted.last) := list_;
          return splitted;
        end if;
      end loop;
    end;
    /
    show errors
    
    declare
      got split_tbl;
    
      procedure print(tbl in split_tbl) as
      begin
        for i in tbl.first .. tbl.last loop
          dbms_output.put_line(i || ' = ' || tbl(i));
        end loop;
      end;
    
    begin
      got := split2('foo,bar,zoo');
      print(got);
      print(split2('1 2 3 4 5', ' '));
    end;
    /
    

    【讨论】:

      【解决方案5】:

      请找到下一个您可能会觉得有用的示例

      --第一个子串

      select substr('alfa#bravo#charlie#delta', 1,  
        instr('alfa#bravo#charlie#delta', '#', 1, 1)-1) from dual;
      

      --第二个子串

      select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 1)+1,  
        instr('alfa#bravo#charlie#delta', '#', 1, 2) - instr('alfa#bravo#charlie#delta', '#', 1, 1) -1) from dual;
      

      --第三个子串

      select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 2)+1,  
        instr('alfa#bravo#charlie#delta', '#', 1, 3) - instr('alfa#bravo#charlie#delta', '#', 1, 2) -1) from dual;
      

      --第 4 个子串

      select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 3)+1) from dual;
      

      最好的问候

      伊曼纽尔

      【讨论】:

        【解决方案6】:

        伙计们,有一个简单的方法。使用替换功能。这是准备传递给 IN 子句的逗号分隔字符串示例。

        在 PL/SQL 中:

        StatusString :=   REPLACE('Active,Completed', ',', ''',''');
        

        在 SQL Plus 中:

        Select  REPLACE('Active,Completed', ',', ''',''') from dual;
        

        【讨论】:

          【解决方案7】:
          function numinstr(p_source in varchar2,p_token in varchar2)
          return pls_integer
          is
              v_occurrence pls_integer := 1;
              v_start pls_integer := 1;
              v_loc pls_integer;
          begin
              v_loc:=instr(p_source, p_token, 1, 1);
              while v_loc > 0 loop
                v_occurrence := v_occurrence+1;
                v_start:=v_loc+1;
                v_loc:=instr(p_source, p_token, v_start, 1);
              end loop;
              return v_occurrence-1;
          end numinstr;
            --
            --
            --
            --
          function get_split_field(p_source in varchar2,p_delim in varchar2,nth pls_integer)
          return varchar2
          is
              v_num_delims pls_integer;
              first_pos pls_integer;
              final_pos pls_integer;
              len_delim pls_integer := length(p_delim);
              ret_len pls_integer;
          begin
              v_num_delims := numinstr(p_source,p_delim);
              if nth < 1 or nth > v_num_delims+1 then
                return null;
              else
                if nth = 1 then
                  first_pos := 1;
                else
                  first_pos := instr(p_source, p_delim, 1, nth-1) + len_delim;
                end if;
                if nth > v_num_delims then
                  final_pos := length(p_source);
                else
                  final_pos := instr(p_source, p_delim, 1, nth) - 1;
                end if;
                ret_len := (final_pos - first_pos) + 1;
                return substr(p_source, first_pos, ret_len);
              end if;
          end get_split_field;
          

          【讨论】:

            【解决方案8】:

            您可以使用 SUBSTR 和 INSTR 的组合,如下所示:

            示例字符串:field = 'DE124028#@$1048708#@$000#@$536967136#@$'

            分隔符是#@$。

            以获取“1048708”为例:

            如果字段是固定长度的(这里是 7):

            substr(field,instr(field,'#@$',1,1)+3,7)
            

            如果字段是可变长度的:

            substr(field,instr(field,'#@$',1,1)+3,instr(field,'#@$',1,2) - (instr(field,'#@$',1,1)+3)) 
            

            您可能应该研究 SUBSTR 和 INSTR 函数以获得更大的灵活性。

            【讨论】:

              【解决方案9】:

              如果APEX_UTIL 不可用,您有一个使用REGEXP_SUBSTR() 的解决方案。

              灵感来自http://nuijten.blogspot.fr/2009/07/splitting-comma-delimited-string-regexp.html

              DECLARE
                I INTEGER;
                TYPE T_ARRAY_OF_VARCHAR IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;
                MY_ARRAY T_ARRAY_OF_VARCHAR;
                MY_STRING VARCHAR2(2000) := '123,456,abc,def';
              BEGIN
                FOR CURRENT_ROW IN (
                  with test as    
                    (select MY_STRING from dual)
                    select regexp_substr(MY_STRING, '[^,]+', 1, rownum) SPLIT
                    from test
                    connect by level <= length (regexp_replace(MY_STRING, '[^,]+'))  + 1)
                LOOP
                  DBMS_OUTPUT.PUT_LINE(CURRENT_ROW.SPLIT);
                  MY_ARRAY(MY_ARRAY.COUNT) := CURRENT_ROW.SPLIT;
                END LOOP;
              END;
              /
              

              【讨论】:

                【解决方案10】:

                这仅适用于 Oracle 10G 及更高版本。

                基本上,您使用 regex_substr 对字符串进行拆分。

                https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement

                【讨论】:

                • 你说的那个页面确实很不错,不过最好把代码复制到这里,这样查看起来更方便快捷。在死链接的情况下也更好......
                【解决方案11】:

                我喜欢那个 apex 实用程序的外观。然而,了解可用于此目的的标准 oracle 函数也是一件好事:subStr 和 inStr http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions001.htm

                【讨论】:

                  猜你喜欢
                  • 2013-02-05
                  • 2021-01-06
                  • 2018-11-10
                  • 2017-06-05
                  • 2018-10-07
                  • 2021-01-12
                  • 1970-01-01
                  • 2015-11-29
                  • 1970-01-01
                  相关资源
                  最近更新 更多