2010-06-29 44 views
7

¿Cómo puedo hacer un paquete que devuelve los resultados en formato de tabla cuando se pasa de los valores csv.cómo convertir CSV a tabla en Oracle

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

deberían volver

one 
two 
three 

Probé algo de ask tom pero que sólo funciona con los tipos de SQL.

Estoy utilizando Oracle 11g. ¿Hay algo incorporado?

Respuesta

10

Las siguientes obras alegar ésta como selecto * de la tabla (divisor ('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

Por desgracia, en 11g todavía tenemos que handroll nuestros propios tokenizers PL/SQL, utilizando los tipos de SQL. En 11gR2 Oracle nos dio una función de agregación para concatenar los resultados en una cadena CSV, por lo que quizás en 12i proporcionarán la capacidad inversa.

Si no desea crear un tipo especial de SQL puede utilizar el incorporado en SYS.DBMS_DEBUG_VC2COLL, así:

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

Aquí está en acción:

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

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

SQL> 

Reconocimiento: este código es una variante de some code I found on Tanel Poder's blog.

0

no tengo 11g instalado para jugar, pero hay es una operación PIVOT y UNPIVOT para convertir columnas a filas/filas en columnas, que puede ser un buen punto de partida.

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

(Después de haber hecho en realidad poco más la situación, esto no parece adecuado para este caso - que funciona con filas/columnas reales, pero no los conjuntos de datos de una columna).

También es DBMS_UTILITY.comma_to_table y table_to_comma para convertir CSV enumera en las tablas de PL/SQL. Existen algunas limitaciones (manejo de avances de línea, etc.) pero puede ser un buen punto de partida.

Mi inclinación sería usar el enfoque TYPE, con una función simple que hace coma_a_table, luego PIPE ROW para cada entrada en el resultado de coma_a_table (lamentablemente, DBMS_UTILITY.comma_to_table es un procedimiento que no puede llamar desde SQL).

3

Aquí es otra solución utilizando un igualador de expresión regular en su totalidad en 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

Para un rendimiento óptimo, es mejor evitar el uso de consultas jerárquicas (CONNECT BY) en la función del divisor.

La siguiente función divisor realiza bastante mejor cuando se aplica a mayores volúmenes de datos

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

Esta diferencia de rendimiento se puede observar a continuación (utilicé el divisor función que se le dio anteriormente en esta discusión).

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>