2009-07-06 12 views
7

voglio essere in grado di dividere stringhe CSV in Oracle 9iCome migliori stringhe spaccatura CSV in Oracle 9i

ho letto il seguente articolo http://www.oappssurd.com/2009/03/string-split-in-oracle.html

Ma non ho capito come fare questo lavoro. Ecco alcune delle mie domande relative ad esso

  1. Questo lavoro in Oracle 9i, in caso contrario, perché no?
  2. C'è un modo migliore per suddividere le stringhe csv e la soluzione presentata sopra?
  3. Devo creare un nuovo tipo? In tal caso, ho bisogno di privilegi specifici per questo?
  4. Posso dichiarare il tipo w/nella funzione?

risposta

8

Ecco un tokenizzatore stringa per Oracle che è un po 'più semplice di quella pagina, ma nessuna idea se è più veloce:

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; 

è possibile utilizzarlo in questo modo:

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

Questo è un ottimo metodo, applicabile anche fino a 11g almeno. Tale metodo è veloce perché non si riavvia l'analisi ogni volta dalla posizione zero, ma continua l'analisi da dove si era interrotto. Ho sviluppato un codice un po 'diverso, pubblicato su [il mio blog su Parsing di una stringa con un CSV in più colonne] (http://hiflitetm.wordpress.com/2013/11/04/parsing-a-string- con-un-csv-in-più-colonne /). È un po 'simile, ma ho seguito il percorso con una funzione pipeline e un modo strano di usare un join cartesiano. Ho dei risultati in singole colonne. – YoYo

2

Sembra che tu non voglia aggiungere schema (tipi, funzione). Un solo modo SQL per analizzare il testo delimitato è "impazzire" con instr e sub call.

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; 

Se siete in cerca di formalizzare una struttura e aggiungendo il codice applicazione appropriata (funzioni, viste, tipi ecc ...) vorrei dare un'occhiata a Tom Kyte di writing su questo subject.

+0

Sì, ho letto il suo articolo http://asktom.oracle.com/pls/asktom/f?p=100 : 11: 0 :::: P11_QUESTION_ID: 2189860818012 – Joyce

+0

Questo non è l'unico modo ... – ErikE

2

Si potrebbe voler essere un po 'più chiaro su ciò che si vuole fare, quindi possiamo darvi una risposta specifica. Mostrare parte del codice è sempre utile :)

Se si utilizzano i parametri, per dividere una stringa di numeri csv (ad esempio: 1,2,3,4), quindi utilizzare quello in una dichiarazione IN dare un'occhiata al funzione str2tbl() in Question 670922. Con alcune modifiche è possibile cambiarlo in uno VARCHAR2 o qualsiasi altra cosa sia necessaria.

Nel seguente è possibile impostare :sMyCatagories pari 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; 

e di utilizzarlo in una dichiarazione prescelta ....

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

Questo è veramente utile solo se si sta utilizzando i parametri. Se metti insieme SQL nella tua applicazione, usa semplicemente una normale istruzione IN.

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

Fondamentalmente, volevo solo togliere i dati csv e voglio vedere se posso farlo senza dover creare un nuovo tipo, dato che non ho il privilegio. – Joyce

15

Joyce,

Ecco tre esempi:

1) Utilizzo di dbms_utility.comma_to_table. Questa non è una routine generica, perché gli elementi dovrebbero essere identificativi validi.Con alcuni trucchetti sporchi possiamo farlo funzionare in modo più universale:

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) Utilizzo del collegamento SQL per livello. Se siete su 10g o superiore è possibile utilizzare il metodo connect-by-livello in combinazione con le espressioni regolari, come questo:

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) Anche in questo caso utilizzando di SQL collegare per livello, ma ora in combinazione con il buon vecchio SUBSTR/INSTR nel caso in cui si è sulla versione 9, come siete:

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. 

si possono vedere alcune più tecniche come questi, in questo blogpost: http://rwijk.blogspot.com/2007/11/interval-based-row-generation.html

Spero che questo aiuti.

Cordiali saluti, Rob.


Per affrontare un commento:

Un esempio inserendo i valori separati in una tabella normalizzata.

Innanzitutto creare le tabelle:

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. 

Perché lei sembra interessato al metodo dbms_utility.comma_to_table, ho citato qui. Tuttavia, non raccomando questa variante, a causa delle stranezze dell'identificatore e a causa dell'elaborazione lenta riga dopo riga.

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. 

Vi consiglio questo sola variante di 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. 

saluti, Rob.

+0

Penso che usare dbms_utility.comma_to_table sia buono. Questo è leggermente fuori tema. Come potrei fare questo correre su una colonna che è piena di questi valori CSV e inserirli tutti in una nuova tabella? Scusa, sono molto nuovo di Oracle. Apprezzalo! Joyce – Joyce

+0

Ho aggiunto una sezione alla risposta per indirizzare il tuo commento. –

+0

Hi Rob, la soluzione n. 2 [Usando la connessione di SQL per livello] funziona alla grande per me.Ma ora ho il requisito di passare 2 stringhe CSV [uguale in lunghezza] come input per PL/SQL stored proc.E ho bisogno di inserire valori da queste due stringhe CSV in due colonne diverse nella tabella.Potresti fammi sapere come fare? – Jimmy

2

ho usato questo, alla fine,

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 ;