【问题标题】:Splitting comma separated string in a PL/SQL stored proc在 PL/SQL 存储过程中拆分逗号分隔的字符串
【发布时间】:2011-04-29 14:18:51
【问题描述】:

我有 CSV 字符串 100.01,200.02,300.03,我需要将其传递给 Oracle 中的 PL/SQL 存储过程。 在 proc 内部,我需要将这些值插入到表中的 Number 列中。

为此,我从这里得到了一种工作方法:

How to best split csv strings in oracle 9i

[2) 使用 SQL 的按级别连接。].

现在,我还有另一个要求。 我需要将 2 个 CSV 字符串 [长度相等] 作为输入传递给 PL/SQL 存储过程。而且,我需要打破这个字符串并将两个 CSV 字符串中的每个值插入到表中的两个不同列中。请让我知道该怎么做吗?

CSV 输入示例: mystring varchar2(2000):='0.75, 0.64, 0.56, 0.45';

myAmount varchar2(2000):= '0.25, 0.5, 0.65, 0.8';

myString 值将进入表 A 列,myAmount 值进入表 B 列。

您能告诉我如何实现吗?

谢谢。

【问题讨论】:

标签: oracle plsql tokenize


【解决方案1】:

这应该可以满足您的需求。它假定您的列表始终只是数字。如果不是这种情况,只需将对 DBMS_SQL.NUMBER_TABLE 的引用更改为适用于所有数据的表类型:

CREATE OR REPLACE PROCEDURE insert_from_lists(
    list1_in IN VARCHAR2,
    list2_in IN VARCHAR2,
    delimiter_in IN VARCHAR2 := ','
)
IS 
    v_tbl1 DBMS_SQL.NUMBER_TABLE;
    v_tbl2 DBMS_SQL.NUMBER_TABLE;

    FUNCTION list_to_tbl
    (
        list_in IN VARCHAR2
    )
    RETURN DBMS_SQL.NUMBER_TABLE
    IS
        v_retval DBMS_SQL.NUMBER_TABLE;
    BEGIN

        IF list_in is not null
        THEN
            /*
            || Use lengths loop through the list the correct amount of times,
            || and substr to get only the correct item for that row
            */
            FOR i in 1 .. length(list_in)-length(replace(list_in,delimiter_in,''))+1
            LOOP
                /*
                || Set the row = next item in the list
                */
                v_retval(i) := 
                        substr (
                            delimiter_in||list_in||delimiter_in,
                            instr(delimiter_in||list_in||delimiter_in, delimiter_in, 1, i  ) + 1,
                            instr (delimiter_in||list_in||delimiter_in, delimiter_in, 1, i+1) - instr (delimiter_in||list_in||delimiter_in, delimiter_in, 1, i) -1
                        );
            END LOOP;
        END IF;

        RETURN v_retval;

    END list_to_tbl;
BEGIN 
   -- Put lists into collections
   v_tbl1 := list_to_tbl(list1_in);
   v_tbl2 := list_to_tbl(list2_in);

   IF v_tbl1.COUNT <> v_tbl2.COUNT
   THEN
      raise_application_error(num => -20001, msg => 'Length of lists do not match');
   END IF;

   -- Bulk insert from collections
   FORALL i IN INDICES OF v_tbl1
      insert into tmp (a, b)
      values (v_tbl1(i), v_tbl2(i));

END insert_from_lists; 

【讨论】:

    【解决方案2】:

    我使用 apex_util.string_to_table 来解析字符串,但如果您愿意,可以使用不同的解析器。然后你可以像这个例子一样插入数据:

    declare
      myString varchar2(2000) :='0.75, 0.64, 0.56, 0.45';
      myAmount varchar2(2000) :='0.25, 0.5, 0.65, 0.8';
      v_array1 apex_application_global.vc_arr2;
      v_array2 apex_application_global.vc_arr2;
    begin
    
      v_array1 := apex_util.string_to_table(myString, ', ');
      v_array2 := apex_util.string_to_table(myAmount, ', ');
    
      forall i in 1..v_array1.count
         insert into mytable (a, b) values (v_array1(i), v_array2(i));
    end;
    

    Apex_util 从 Oracle 10G 开始可用。在此之前,它被称为 htmldb_util,默认情况下没有安装。如果您不能使用它,您可以使用我多年前编写并发布的字符串解析器here

    【讨论】:

    • 感谢您的回复。但是,我使用的是不支持 apex_util 的 Oracle 9i。
    • 没关系,您可以使用任何“标记器”功能,例如我发布的链接中的功能或您在问题中链接到的相关问题的已接受答案中的功能。只需为每个要标记的字符串调用一次。
    【解决方案3】:

    这是一个很好的解决方案:

    FUNCTION comma_to_table(iv_raw IN VARCHAR2) RETURN dbms_utility.lname_array IS
       ltab_lname dbms_utility.lname_array;
       ln_len     BINARY_INTEGER;
    BEGIN
       dbms_utility.comma_to_table(list   => iv_raw
                                  ,tablen => ln_len
                                  ,tab    => ltab_lname);
       FOR i IN 1 .. ln_len LOOP
          dbms_output.put_line('element ' || i || ' is ' || ltab_lname(i));
       END LOOP;
       RETURN ltab_lname;
    END;
    

    来源:CSV - comma separated values - and PL/SQL(链接不再有效)

    【讨论】:

    • 感谢您的评论,但是,这个函数是否返回一个数组?如果是,那么我将在 proc 中对其进行迭代以在表中插入值。
    • lname_array 是一个表:TYPE lname_array IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER; -- 所以你可以从中选择
    • 一个好的开始,但这里需要做更多的工作:您不能SELECT FROM 在包中声明的 PL/SQL 数组类型。如果您在架构级别声明了一个表类型,则可以从中选择,如果您使用 TABLE() 运算符对其进行转换。
    • 恐怕这不适用于像 '0.25' 这样的值,因为 dbms_utility.comma_to_table 只能解析有效标识符的值列表 - 即最多 30 个字符,以字母开头或括起来用双引号等。
    • 是的,Tony,这是正确的。已经尝试过这种方法。它不适用于数字。
    【解决方案4】:

    我不确定这是否适合您的 oracle 版本。在我的 10g 上,我可以使用流水线表函数:

    set serveroutput on
    
    create type number_list as table of number;
    
    -- since you want this solution
    create or replace function split_csv (i_csv varchar2) return number_list pipelined 
      is 
        mystring varchar2(2000):= i_csv;
      begin
        for r in
        ( select regexp_substr(mystring,'[^,]+',1,level) element
            from dual
         connect by level <= length(regexp_replace(mystring,'[^,]+')) + 1
        )
        loop
          --dbms_output.put_line(r.element);
          pipe row(to_number(r.element, '999999.99'));
        end loop;
      end;
    /
    
    insert into foo
    select column_a,column_b from 
      (select column_value column_a, rownum rn from table(split_csv('0.75, 0.64, 0.56, 0.45'))) a 
     ,(select column_value column_b, rownum rn from table(split_csv('0.25, 0.5, 0.65, 0.8'))) b
     where a.rn = b.rn
    ;
    

    【讨论】:

      【解决方案5】:
      CREATE OR REPLACE PROCEDURE insert_into (
         p_errcode        OUT   NUMBER,
         p_errmesg        OUT   VARCHAR2,
         p_rowsaffected   OUT   INTEGER
      )
      AS
         v_param0   VARCHAR2 (30) := '0.25,2.25,33.689, abc, 99';
         v_param1   VARCHAR2 (30) := '2.65,66.32, abc-def, 21.5';
      BEGIN
         FOR i IN (SELECT COLUMN_VALUE
                     FROM TABLE (SPLIT (v_param0, ',')))
         LOOP
            INSERT INTO tempo
                        (col1
                        )
                 VALUES (i.COLUMN_VALUE
                        );
         END LOOP;
      
         FOR i IN (SELECT COLUMN_VALUE
                     FROM TABLE (SPLIT (v_param1, ',')))
         LOOP
            INSERT INTO tempo
                        (col2
                        )
                 VALUES (i.COLUMN_VALUE
                        );
         END LOOP;
      END;
      

      【讨论】:

        【解决方案6】:
        create or replace procedure pro_ss(v_str varchar2) as
        v_str1 varchar2(100); 
        v_comma_pos number := 0;    
        v_start_pos number := 1;
        begin             
            loop        
            v_comma_pos := instr(v_str,',',v_start_pos);   
            if  v_comma_pos = 0 then     
              v_str1 := substr(v_str,v_start_pos);  
              dbms_output.put_line(v_str1);    
              exit;
              end if;    
            v_str1 := substr(v_str,v_start_pos,(v_comma_pos - v_start_pos)); 
            dbms_output.put_line(v_str1);       
            v_start_pos := v_comma_pos + 1;    
            end loop; 
        end;
        /
        
        call pro_ss('aa,bb,cc,dd,ee,ff,gg,hh,ii,jj');
        

        输出:aa bb cc dd ee ff gg hh ii jj

        【讨论】:

          【解决方案7】:

          已经提供了许多好的解决方案。但是,如果他的文本以非常简单的逗号分隔格式或类似格式提供,并且速度很重要,那么我为您提供了一个带有 TABLE 函数的解决方案(在 PL/SQL 中)。我还提供了一些其他解决方案的概要。

          请在Blog Entry on Parsing a CSV into multiple columns上查看更多信息。

          【讨论】:

          • 如果出现反对票,我将不胜感激。我提供的解决方案是广泛的性能调整和研究的结果。如果您认为某些事情是错误的或被歪曲的,我可以知道投反对票的原因是什么。
          • 我没有投反对票,但可能是因为这个答案在很大程度上取决于链接。
          【解决方案8】:

          对于connect by 用例,这种方法应该适合您:

          select regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level)
          from dual
          connect by regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null;
          

          【讨论】:

            猜你喜欢
            • 2014-05-30
            • 2011-12-24
            • 2017-06-05
            • 1970-01-01
            • 2021-01-12
            • 1970-01-01
            • 1970-01-01
            • 2011-10-25
            • 2018-12-21
            相关资源
            最近更新 更多