2010-06-29 16 views
7

Jak mogę utworzyć pakiet, który zwraca wyniki w formacie tabeli po przekazaniu w wartości csv.jak przekonwertować CSV na tabelę w Oracle

select * from table(schema.mypackage.myfunction('one, two, three')) 

powinien powrócić

one 
two 
three 

Próbowałem coś z ask tom ale to działa tylko z SQL typów.

Używam wyroczni 11g. Czy jest coś wbudowanego?

Odpowiedz

10

następujące prace powołać go jako select * from tabela (splitter ('a, b, c, d'))

create or replace function splitter(p_str in varchar2) return sys.odcivarchar2list 
is 
v_tab sys.odcivarchar2list:=new sys.odcivarchar2list(); 
begin 
with cte as (select level ind from dual 
connect by 
level <=regexp_count(p_str,',') +1 
) 
select regexp_substr(p_str,'[^,]+',1,ind) 
bulk collect into v_tab 
from cte; 
return v_tab; 
end; 
/
4

Niestety, w 11g nadal musimy samodzielnie zarządzać własnymi tokenizatorami PL/SQL, używając typów SQL. W 11gR2 Oracle dało nam funkcję agregującą, aby połączyć wyniki w łańcuch CSV, więc być może w 12i zapewnią odwrotną zdolność.

Jeśli nie chcesz, aby utworzyć typ SQL szczególnie można użyć wbudowanej SYS.DBMS_DEBUG_VC2COLL, tak:

create or replace function string_tokenizer 
    (p_string in varchar2 
     , p_separator in varchar2 := ',') 
    return sys.dbms_debug_vc2coll 
is 
    return_value SYS.DBMS_DEBUG_VC2COLL; 
    pattern varchar2(250); 
begin 

    pattern := '[^('''||p_separator||''')]+' ; 

    select trim(regexp_substr (p_string, pattern, 1, level)) token 
    bulk collect into return_value 
    from dual 
    where regexp_substr (p_string, pattern, 1, level) is not null 
    connect by regexp_instr (p_string, pattern, 1, level) > 0; 

    return return_value; 

end string_tokenizer; 
/

Oto ona w akcji:

SQL> select * from table (string_tokenizer('one, two, three')) 
    2/

COLUMN_VALUE 
---------------------------------------------------------------- 
one 
two 
three 

SQL> 

Potwierdzenie: ten kod jest odmianą some code I found on Tanel Poder's blog.

0

nie mam 11g zainstalowane do zabawy, ale nie jest operacją PIVOT i UNPIVOT dla konwertowania kolumn na wiersze/wiersze na kolumny, co może być dobrym punktem wyjścia.

http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-pivot.html

(Po rzeczywiście zrobić kilka dalszych badań, to nie wygląda nadaje się do tej sprawy - to działa z rzeczywistymi wierszy/kolumn, ale nie zestawów danych w kolumnie).

Istnieje również DBMS_UTILITY.comma_to_table i table_to_comma do konwersji list CSV do tabel pl/sql. Istnieją pewne ograniczenia (obsługa podawania linii itp.), Ale może to być dobry punkt wyjścia.

Moja inklinacja to użycie podejścia TYPE, z prostą funkcją, która robi comma_to_table, następnie PIPE ROW dla każdego wpisu w wyniku comma_to_table (niestety, DBMS_UTILITY.comma_to_table jest procedurą, więc nie można wywołać z SQL).

3

Oto inne rozwiązanie z użyciem wyrażeń regularnych w całości w sql.

SELECT regexp_substr('one,two,three','[^,]+', 1, level) abc 
FROM dual 
CONNECT BY regexp_substr('one,two,three', '[^,]+', 1, level) IS NOT NULL 
2

Aby uzyskać optymalną wydajność, najlepiej unikać hierarchicznych zapytań (CONNECT BY) w funkcji rozgałęźnika.

Następujące funkcje rozdzielacz dokonuje znacznie lepsze, gdy stosuje się większe ilości danych

CREATE OR REPLACE FUNCTION row2col(p_clob_text IN VARCHAR2) 
    RETURN sys.dbms_debug_vc2coll PIPELINED 
IS 
    next_new_line_indx PLS_INTEGER; 
    remaining_text VARCHAR2(20000); 
    next_piece_for_piping VARCHAR2(20000); 
    BEGIN 

    remaining_text := p_clob_text; 
    LOOP 
     next_new_line_indx := instr(remaining_text, ','); 
     next_piece_for_piping := 
      CASE 
      WHEN next_new_line_indx <> 0 THEN 
       TRIM(SUBSTR(remaining_text, 1, next_new_line_indx-1)) 
      ELSE 
       TRIM(SUBSTR(remaining_text, 1)) 
      END; 

     remaining_text := SUBSTR(remaining_text, next_new_line_indx+1); 
     PIPE ROW(next_piece_for_piping); 
     EXIT WHEN next_new_line_indx = 0 OR remaining_text IS NULL; 
    END LOOP; 
    RETURN; 
    END row2col; 
/

Ta różnica wyników można zaobserwować poniżej (I stosuje się rozdzielacz funkcyjne jak podano wcześniej w dyskusji).

SQL> SET TIMING ON 
SQL> 
SQL> WITH SRC AS (
    2 SELECT rownum||',a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'||rownum txt 
    3 FROM DUAL 
    4 CONNECT BY LEVEL <=10000 
    5 ) 
    6 SELECT NULL 
    7 FROM SRC, TABLE(SYSTEM.row2col(txt)) t 
    8 HAVING MAX(t.column_value) > 'zzz' 
    9 ; 

no rows selected 

Elapsed: 00:00:00.93 
SQL> 
SQL> WITH SRC AS (
    2 SELECT rownum||',a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'||rownum txt 
    3 FROM DUAL 
    4 CONNECT BY LEVEL <=10000 
    5 ) 
    6 SELECT NULL 
    7 FROM SRC, TABLE(splitter(txt)) t 
    8 HAVING MAX(t.column_value) > 'zzz' 
    9 ; 

no rows selected 

Elapsed: 00:00:14.90 
SQL> 
SQL> SET TIMING OFF 
SQL> 
Powiązane problemy