2009-07-06 14 views
7

quiero ser capaz de dividir cadenas CSV en Oracle 9imejores cadenas de división CSV en Oracle 9i

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

Pero yo no entendía 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. ¿Existe una mejor manera de dividir las cadenas de CSV que la solución presentada anteriormente?
  3. ¿Debo crear un tipo nuevo? Si es así, ¿necesito privilegios específicos para eso?
  4. ¿Puedo declarar el tipo w/en la función?

Respuesta

8

He aquí una tokenizer cadena 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; 

Usted puede utilizar de esta manera:

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

Este es un método muy bueno, aún aplicable incluso hasta 11 g como mínimo. Tal método es rápido porque no reinicias tu análisis cada vez desde la posición cero, sino que continúas analizando desde donde lo dejaste. He desarrollado un código que lo maneja un poco diferente, publicado en [mi blog sobre el análisis de una cadena con un CSV en varias columnas] (http://hiflitetm.wordpress.com/2013/11/04/parsing-a-string- with-a-csv-into-multiple-columns /). Es algo similar, pero seguí la ruta con una función segmentada y una forma extraña de usar una combinación cartesiana. Tengo resultados en columnas individuales. – YoYo

2

Parece que no desea agregar esquema (tipos, función). Una única forma de SQL para analizar el texto delimitado es 'enloquecer' con las llamadas substr y substr.

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 Tom Kyte de writing en este subject.

+0

Sí, he leído su artículo http://asktom.oracle.com/pls/asktom/f?p=100 : 11: 0 :::: P11_QUESTION_ID: 2189860818012 – Joyce

+0

Esa no es la única forma ... – ErikE

2

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

Si está utilizando parámetros, para dividir una cadena de números CSV (por ejemplo: 1,2,3,4) y luego usar eso en una declaración IN eche un vistazo a la función str2tbl() en Question 670922. Con algunos cambios, puede cambiarlo a VARCHAR2 o lo que sea que necesite.

En la 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á conectando SQL en su aplicación, simplemente use una instrucción IN normal.

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

Básicamente, solo quería desvirtuar datos csv, y quiero ver si puedo hacerlo sin tener que crear un tipo nuevo, ya que no tienes el privilegio – Joyce

15

Joyce,

Éstos son tres ejemplos:

1) Usando dbms_utility.comma_to_table. Esta no es una rutina de propósito general, porque 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) Usando SQL conecta 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, así:

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) usando vez de SQL conectar por nivel, pero ahora en combinación con el bueno de SUBSTR/INSTR en caso de que se encuentran en la versión 9, al igual que usted es:

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 pueden 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 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. 

porque parece interesado en el enfoque dbms_utility.comma_to_table, menciono aquí. Sin embargo, ciertamente no recomiendo esta variante, debido a las peculiaridades de los identificadores y al lento procesamiento de filas por filas.

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 no recomendaría este sola variante SQL:

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.

+0

Creo que usar dbms_utility.comma_to_table es bueno. Esto está ligeramente fuera de tema. ¿Cómo puedo hacer que esto se ejecute en una columna que está llena de estos valores de csv e insertarlos todos en una nueva tabla? Lo siento, soy muy nuevo para Oracle. ¡Lo aprecio! Joyce – Joyce

+0

Agregué una sección a la respuesta para dirigir su comentario. –

+0

Hola Rob, la solución n. ° 2 [Usar SQL's connect por nivel] funciona muy bien para mí.Pero ahora, tengo un requisito de pasar 2 cadenas de CSV [igual de longitud] como entrada para el proceso almacenado de PL/SQL. Y necesito insertar valores de estas dos cadenas de CSV en dos columnas diferentes en la tabla. ¿Podría por favor? déjame saber cómo hacerlo? – Jimmy

2

He 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 ; 
Cuestiones relacionadas