2010-10-23 6 views
21

Ho una stringa CSV 100.01,200.02.300.03 che devo passare a una procedura memorizzata PL/SQL in Oracle. All'interno del proc, ho bisogno di inserire questi valori in una colonna Numero nella tabella.Divisione di una stringa separata da virgola in un processo memorizzato PL/SQL

Per questo, ho avuto un approccio di lavoro da qui:

How to best split csv strings in oracle 9i

[2) Utilizzo di SQL di connettersi per livello.].

Ora, ho un altro requisito. Ho bisogno di passare 2 stringhe CSV [uguale in lunghezza] come input per PL/SQL memorizzato proc.E ho bisogno di rompere questa stringa e inserire ogni valore da due stringhe CSV in due colonne diverse nella tabella.Potresti io so come fare?

Esempio di ingressi CSV: mystring varchar2 (2000): = '0,75, 0,64, 0,56, 0,45';

myAmount varchar2 (2000): = '0,25, 0,5, 0,65, 0,8';

I valori di myString passano nei valori Column A e myAmount nella colonna B della tabella.

Potrebbe farmi sapere come ottenere questo?

Grazie.

+0

Verificare http://nuijten.blogspot.com/2009/07/splitting-comma-delimited-strin g-regexp.html - usando le espressioni regolari in Oracle – InSane

risposta

7

Questo dovrebbe fare ciò che stai cercando .. Presume che la tua lista sarà sempre solo numeri. Se questo non è il caso, basta cambiare i riferimenti a DBMS_SQL.NUMBER_TABLE ad un tipo di tabella che funziona per tutti i dati:

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; 
7

Ecco una buona soluzione:

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; 

Fonte: CSV - comma separated values - and PL/SQL (link non più validi)

+0

grazie per il tuo commento, ma questa funzione restituisce un array? Se sì, allora avrò iterato su di esso nel proc per inserire i valori nella tabella. – Jimmy

+0

lname_array è una tabella: TYPE lname_array IS TABLE OF VARCHAR2 (4000) INDEX BY BINARY_INTEGER; - Quindi puoi semplicemente SELECT da esso –

+1

un buon inizio ma un po 'più di lavoro richiesto qui: non puoi 'SELECT FROM' un tipo di array PL/SQL dichiarato in un pacchetto. Se hai dichiarato un tipo di tabella a livello di schema, puoi selezionarlo da esso, se lo lanci con l'operatore 'TABLE()'. –

3

io non sono sicuro se questo si inserisce la vostra versione di Oracle. Sul mio 10g posso utilizzare funzioni del tavolo pipeline:

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 
; 
6

Io uso apex_util.string_to_table per analizzare le stringhe, ma è possibile utilizzare un parser diverso se lo si desidera. Quindi è possibile inserire i dati come in questo esempio:

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 è disponibile da Oracle 10G in poi. Prima si chiamava htmldb_util e non era installato di default. Se non è possibile utilizzarlo, è possibile utilizzare l'analizzatore di stringhe che ho scritto molti anni fa e pubblicato here.

+0

Grazie per la risposta. Ma, sto usando Oracle 9i che non supporta apex_util. – Jimmy

+0

Va bene, puoi usare qualsiasi funzione "tokenizer" come quella nel link che ho postato o quella nella risposta accettata alla domanda correlata a cui hai collegato nella tua domanda. Basta chiamarlo una volta per ogni stringa da tokenizzare. –

3
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; 
-1

Molte buone soluzioni sono state già fornite. Tuttavia, se il testo è fornito in un formato delimitato da virgolette molto semplice o simile, e la velocità è importante, allora ho per te una soluzione con una funzione TABLE (in PL/SQL). Ho anche fornito una carrellata di alcune altre soluzioni.

Per ulteriori informazioni, vedere Blog Entry on Parsing a CSV into multiple columns.

+0

In caso di votazione negativa, gradirei un commento. La soluzione che ho fornito è il risultato di un'intensa attività di ottimizzazione e ricerca delle prestazioni. Se credi che qualcosa sia sbagliato o travisato, potrei beneficiare di sapere qual è il motivo del voto negativo. – YoYo

+1

Non ho votato, ma probabilmente perché questa risposta dipende molto da un collegamento. –

-1

Per quanto riguarda il caso connect by uso, questo approccio dovrebbe funzionare per voi:

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

creare o sostituire procedure pro_ss(v_str varchar2) come

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