Pregunta

Quiero ser capaz de dividir cadenas CSV en Oracle 9i

He leído el siguiente artículo http://www.oappssurd.com/2009/03/string -split-in-oracle.html

Pero no entiendo cómo hacer este trabajo. Estas son algunas de mis preguntas que le son propias

  1. Que este trabajo en Oracle 9i, si no, ¿por qué no?
  2. ¿Hay una mejor manera de hacer las cadenas CSV dividir entonces la solución presentada anteriormente?
  3. ¿Es necesario crear un nuevo tipo? Si es así, ¿necesito privilegios que específicos para eso?
  4. ¿Puedo declarar el tipo w / en la función?
¿Fue útil?

Solución

Aquí hay una cadena de tokenizer para Oracle que es un poco más sencillo que esa página, pero ni idea de si es tan rápido:

create or replace function splitter_count(str in varchar2, delim in char) return int as
val int;
begin
  val := length(replace(str, delim, delim || ' '));
  return val - length(str); 
end;

create type token_list is varray(100) of varchar2(200);

CREATE or replace function tokenize (str varchar2, delim char) return token_list as
ret token_list;
target int;
i int;
this_delim int;
last_delim int;
BEGIN
  ret := token_list();
  i := 1;
  last_delim := 0;
  target := splitter_count(str, delim);
  while i <= target
  loop
    ret.extend();
    this_delim := instr(str, delim, 1, i);
    ret(i):= substr(str, last_delim + 1, this_delim - last_delim -1);
    i := i + 1;
    last_delim := this_delim;
  end loop;
  ret.extend();
  ret(i):= substr(str, last_delim + 1);
  return ret;
end;

Puede utilizar de esta manera:

select tokenize('hi you person', ' ') from dual;
VARCHAR(hi,you,person)

Otros consejos

Joyce,

Aquí hay tres ejemplos:

1) Utilizando dbms_utility.comma_to_table. Esto no es una rutina de propósito general, debido a que los elementos deben ser identificadores válidos. Con algunos trucos sucios podemos hacer que funcione más universal:

SQL> declare
  2    cn_non_occuring_prefix constant varchar2(4) := 'zzzz';
  3    mystring varchar2(2000):='a:sd:dfg:31456:dasd: :sdfsdf'; -- just an example
  4    l_tablen binary_integer;
  5    l_tab    dbms_utility.uncl_array;
  6  begin
  7    dbms_utility.comma_to_table
  8    ( list   => cn_non_occuring_prefix || replace(mystring,':',','||cn_non_occuring_prefix)
  9    , tablen => l_tablen
 10    , tab    => l_tab
 11    );
 12    for i in 1..l_tablen
 13    loop
 14      dbms_output.put_line(substr(l_tab(i),1+length(cn_non_occuring_prefix)));
 15    end loop;
 16  end;
 17  /
a
sd
dfg
31456
dasd

sdfsdf

PL/SQL-procedure is geslaagd.

2) El uso de conexión de SQL por nivel. Si usted está en 10g o superior puede usar el método de conexión por nivel en combinación con las expresiones regulares, como esto:

SQL> declare
  2    mystring varchar2(2000):='a:sd:dfg:31456:dasd: :sdfsdf'; -- just an example
  3  begin
  4    for r in
  5    ( select regexp_substr(mystring,'[^:]+',1,level) element
  6        from dual
  7     connect by level <= length(regexp_replace(mystring,'[^:]+')) + 1
  8    )
  9    loop
 10      dbms_output.put_line(r.element);
 11    end loop;
 12  end;
 13  /
a
sd
dfg
31456
dasd

sdfsdf

PL/SQL-procedure is geslaagd.

3) Una vez más el uso de conexión por nivel, pero ahora en combinación con el bueno de SUBSTR / INSTR en caso de que usted está en la versión 9, como si SQL:

    SQL> declare
      2    mystring varchar2(2000):='a:sd:dfg:31456:dasd: :sdfsdf'; -- just an example
      3  begin
      4    for r in
      5    ( select substr
      6             ( str
      7             , instr(str,':',1,level) + 1
      8             , instr(str,':',1,level+1) - instr(str,':',1,level) - 1
      9             ) element
     10        from (select ':' || mystring || ':' str from dual)
     11     connect by level <= length(str) - length(replace(str,':')) - 1
     12    )
     13    loop
     14      dbms_output.put_line(r.element);
     15    end loop;
     16  end;
     17  /
    a
    sd
    dfg
    31456
    dasd

    sdfsdf

PL/SQL-procedure is geslaagd.

Se puede ver algunas más técnicas como éstas, en esta entrada de blog: http://rwijk.blogspot.com/2007/11/interval-based-row-generation.html

Espero que esto ayude.

Saludos, Rob.


Para hacer frente a su comentario:

Un ejemplo de la inserción de los valores separados en una tabla normalizada.

En primer lugar crear las tablas:

SQL> create table csv_table (col)
  2  as
  3  select 'a,sd,dfg,31456,dasd,,sdfsdf' from dual union all
  4  select 'a,bb,ccc,dddd' from dual union all
  5  select 'zz,yy,' from dual
  6  /

Table created.

SQL> create table normalized_table (value varchar2(10))
  2  /

Table created.

Debido a que usted parece interesado en el enfoque dbms_utility.comma_to_table, menciono aquí. Sin embargo, ciertamente no recomiendo esta variante, debido a las peculiaridades de identificador y debido a la lenta fila por fila procesamiento.

SQL> declare
  2    cn_non_occuring_prefix constant varchar2(4) := 'zzzz';
  3    l_tablen binary_integer;
  4    l_tab    dbms_utility.uncl_array;
  5  begin
  6    for r in (select col from csv_table)
  7    loop
  8      dbms_utility.comma_to_table
  9      ( list   => cn_non_occuring_prefix || replace(r.col,',',','||cn_non_occuring_prefix)
 10      , tablen => l_tablen
 11      , tab    => l_tab
 12      );
 13      forall i in 1..l_tablen
 14        insert into normalized_table (value)
 15        values (substr(l_tab(i),length(cn_non_occuring_prefix)+1))
 16      ;
 17    end loop;
 18  end;
 19  /

PL/SQL procedure successfully completed.

SQL> select * from normalized_table
  2  /

VALUE
----------
a
sd
dfg
31456
dasd

sdfsdf
a
bb
ccc
dddd
zz
yy


14 rows selected.

Yo te recomiendo esta variante SQL sola:

SQL> truncate table normalized_table
  2  /

Table truncated.

SQL> insert into normalized_table (value)
  2   select substr
  3          ( col
  4          , instr(col,',',1,l) + 1
  5          , instr(col,',',1,l+1) - instr(col,',',1,l) - 1
  6          )
  7     from ( select ',' || col || ',' col from csv_table )
  8        , ( select level l from dual connect by level <= 100 )
  9    where l <= length(col) - length(replace(col,',')) - 1
 10  /

14 rows created.

SQL> select * from normalized_table
  2  /

VALUE
----------
a
a
zz
sd
bb
yy
dfg
ccc

31456
dddd
dasd

sdfsdf

14 rows selected.

Saludos, Rob.

Es posible que desee ser un poco más clara de lo que quiere hacer, entonces podemos darle una respuesta específica. Mostrando algo de su código es siempre útil:)

Si está utilizando parametros, para dividir una cadena de números csv (por ejemplo: 1,2,3,4) y luego utilizar eso en una declaración IN echar un vistazo a la str2tbl() función en Question 670.922 . Con algunos cambios que podría cambiarlo a un VARCHAR2 o lo que sea necesario.

En el siguiente podría configurar :sMyCatagories igual a '1,2,3,4'

create or replace type myTableType as table of number;

create or replace function str2tbl( p_str in varchar2 ) return myTableType
  as
     l_str   long default p_str || ',';
     l_n        number;
     l_data    myTableType := myTabletype();
  begin
      loop
          l_n := instr( l_str, ',' );
          exit when (nvl(l_n,0) = 0);
          l_data.extend;
          l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
          l_str := substr( l_str, l_n+1 );
      end loop;
      return l_data;
  end;

y utilizarlo en una instrucción de selección ....

SELECT 
  *
FROM
  atable a 
WHERE 
  a.category in (
        select * from INLIST (
           select cast(str2tbl(:sMyCatagories) as mytableType) from dual
        ) 
  );

Esto es realmente sólo es útil si está utilizando parámetros. Si está Munging juntos SQL en su aplicación, a continuación, sólo tiene que utilizar una normal en el comunicado.

SELECT 
  *
FROM
  atable a 
WHERE 
  a.category in (1,2,3,4);

I utilizado este en el extremo

create or replace function split
(
   p_list varchar2

) return sys.dbms_debug_vc2coll pipelined
is
   l_idx    pls_integer;
   l_list    varchar2(32767) := p_list;
   l_value    varchar2(32767);
begin
   loop
       l_idx := instr(l_list,',');
       if l_idx > 0 then
           pipe row(substr(l_list,1,l_idx-1));
           l_list := substr(l_list,l_idx+length(','));

       else
           pipe row(l_list);
           exit;
       end if;
   end loop;
   return;
end split;

declare
CURSOR c IS  select occurrence_num, graphics from supp where graphics is not null and graphics not like ' %';
begin
  FOR r IN c LOOP   
      insert into image (photo_id,report_id, filename) 
      select image_key_seq.nextval   photo_id, r.occurrence_num report_id, 
      t.column_value  filename from table(split(cast(r.graphics as varchar2(1000)))) t where t.column_value is not null;
   END LOOP;  
end ;

Parece que usted no desea añadir esquema (tipos, función). Uno de SQL única manera de analizar el texto delimitado es a 'volverse loco' con Instr y substr llamadas.

    DECLARE
      V_CSV_STRING VARCHAR2(100);
    BEGIN
      --Create a test delimited list of first_name, last_name, middle_init
      V_CSV_STRING := 'Brian,Hart,M';

    select substr( V_CSV_STRING||',', 1, instr(V_CSV_STRING,',')-1 ) FIRST_NAME,
           substr( V_CSV_STRING||',,', instr( V_CSV_STRING||',,', ',') +1, 
                             instr( V_CSV_STRING||',,', ',', 1, 2 )-instr(V_CSV_STRING||',,',',')-1 ) LAST_NAME,
           rtrim(substr( V_CSV_STRING||',,', instr( V_CSV_STRING||',,',',',1,2)+1),',') MIDDLE_INIT
     from dual;
     END;

Si lo que buscas para formalizar una estructura y añadiendo el código de la aplicación apropiada (funciones, vistas, tipos, etc ...) Me gustaría echar un vistazo a escribir en este sujeto

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top