Разделение строки через запятую в хранимом процессе PL / SQL - PullRequest
22 голосов
/ 23 октября 2010

У меня есть строка CSV 100.01,200.02,300.03, которую нужно передать хранимой процедуре PL / SQL в Oracle. Внутри процедуры мне нужно вставить эти значения в столбец Number таблицы.

Для этого я получил рабочий подход отсюда:

Как лучше разделить строки CSV в Oracle 9i

[2) Использование соединения SQL по уровням.].

Теперь у меня есть другое требование. Мне нужно передать 2 строки CSV [равной длины] в качестве входных данных для PL / SQL, хранящегося в proc.A, и мне нужно разбить эту строку и вставить каждое значение из двух строк 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 таблицы.

Не могли бы вы дать мне знать, как этого добиться?

Спасибо.

Ответы [ 8 ]

8 голосов
/ 27 октября 2010

Это должно сделать то, что вы ищете .. Предполагается, что ваш список всегда будет просто числа. Если это не так, просто измените ссылки на 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; 
8 голосов
/ 23 октября 2010

Вот хорошее решение:

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 - значения через запятую - и PL / SQL (ссылка больше не действительна)

6 голосов
/ 27 октября 2010

Я использую 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 и не был установлен по умолчанию. Если вы не можете использовать это, вы можете использовать анализатор строк, который я написал много лет назад и разместил здесь .

3 голосов
/ 27 июля 2012
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;
3 голосов
/ 27 октября 2010

Я не уверен, подходит ли это вашей версии оракула.На моем 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
;
2 голосов
/ 05 августа 2015
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');

outout: aa bb cc dd ee ff gg hh ii jj

0 голосов
/ 12 января 2015

Что касается варианта использования 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;
0 голосов
/ 07 ноября 2013

Многие хорошие решения уже были предоставлены.Однако, если текст представлен в очень простом формате с разделителями-запятыми или аналогичным, и скорость важна, то у меня есть для вас решение с функцией TABLE (в PL / SQL).Я также предоставил краткое изложение некоторых других решений.

Пожалуйста, смотрите больше в Записи блога о разборе CSV на несколько столбцов .

...