miércoles, 4 de febrero de 2009

Cómo pasar valores desde sqlplus hacia Unix Shell

Hay ocasiones que dentro de un Shell script queremos consultar la base de datos y con dicho resultado continuar la lógica de nuestro Shell. Como logramos transferir ese resultado?

Tenemos dos enfoques para capturar valores luego de terminada la ejecución de Sql*plus: por archivos y por variables.

La primera de ellas es básicamente escribir la salida de la consulta en un archivo y luego levantarlo desde Shell. Este caso es ideal cuando nuestra consulta retorna varios registros, ya que el archivo servirá de entrada a algún comando Unix para procesarlo línea a línea. No veremos un ejemplo ya que es muy sencillo, basta usar el comando SPOOL de Sqlplus para obtener la salida en un archivo de texto. Si bien no es del todo prolijo, funciona.

Y cuando digo prolijidad, me refiero a que en realidad generar un archivo temporal no es necesario, ya que podemos recoger el resultado de la consulta en una variable Shell directamente. Lo veremos con algunos ejemplos hechos para Solaris 10. Algunos parámetros pueden variar respecto a Linux, se recomienda acudir a la documentación de man.

Resultado único
Supongamos que quiero leer el contenido de la columna Dummy de la tabla Dual y continuar manipulando el valor en Shell.

Unix lo hace fácil: ejecutamos la consulta en una sesión sqlplus, y la salida es capturada en la variable 'resultado'. Para simplificar este ejemplo no he considerado si hubo algún error en la consulta, pero puede agregarse lógica que trate mensajes de error dentro de la variable asignada.

#!/bin/ksh
resultado=`sqlplus -s 'scott/tiger' << EOF
set serveroutput on
set feedback off
set head off
select * from dual;
exit;
EOF`

echo "El resultado es: $resultado"

Varios resultados en una línea
Qué hacer si necesitamos retornar más de un valor? Continuamos retornando una única línea, pero separamos los valores dentro del sql con algún caracter que no ocurra dentro de cada resultado, por ejemplo punto y coma. En el ejemplo obtenemos el usuario conectado, la fecha actual y el valor de la columna de Dual, todo al mismo tiempo.
#!/bin/ksh
resultado=`sqlplus -s 'scott/tiger' << EOF
set serveroutput on
set feedback off
set head off
select user||';'||sysdate||';'||dummy from dual;
exit;
EOF`

echo "Los valores son: $resultado"
El valor retornado es: SCOTT;05-FEB-09;X

Con el comando cut separamos los valores fácilmente:

echo $resultado | cut -d';' -f1
SCOTT
echo $resultado | cut -d';' -f2
05-FEB-09
echo $resultado | cut -d';' -f3
X

Resultados multilínea
Si queremos obtener varias líneas en lugar de una sola, también podemos hacerlo de esta forma. Como en el caso anterior, si tenemos múltiples valores por línea es aconsejable usar separadores, ya que los espacios no son buenos a la hora de identificar strings que contengan espacios. Además, evitamos los incómodos espacios entre líneas al optimizar el tamaño de cada cadena y no llegar al fin de cada línea.

#!/bin/ksh
resultado=`sqlplus -s 'scott/tiger' << EOF
set serveroutput on
set feedback off
set head off
set linesize 131
set pagesize 9999

select empno||';'||ename||';'||job||';'||mgr||';'||deptno from emp;
exit;
EOF`

echo "El resultado es: $resultado"


La salida en este caso es:

7369;SMITH;CLERK;7902;20
7499;ALLEN;SALESMAN;7698;30
7521;WARD;SALESMAN;7698;30
7566;JONES;MANAGER;7839;20
7654;MARTIN;SALESMAN;7698;30
7698;BLAKE;MANAGER;7839;30
7782;CLARK;MANAGER;7839;10
7788;SCOTT;ANALYST;7566;20
7839;KING;PRESIDENT;;10
7844;TURNER;SALESMAN;7698;30
7876;ADAMS;CLERK;7788;20
7900;JAMES;CLERK;7698;30
7902;FORD;ANALYST;7566;20


Ahora, podemos usar cualquier comando para tratar las líneas. Uno de mis favoritos es awk, ya que nos provee de muchas funciones para tratamiento de cada tipo.
Para que awk consuma cada línea de la variable como si fuese un archivo, debemos incluir comillas dobles, de otro modo lo considerará como una única gran línea.
En el siguiente ejemplo, vemos como awk toma línea a línea e imprime un texto anexo.

echo "$resultado" | awk -F";" 'BEGIN {$cnt=1}
{print "Linea "$cnt, $0; $cnt=$cnt+1;}'


La salida generada por awk es:

Linea 1 7369;SMITH;CLERK;7902;20
Linea 2 7499;ALLEN;SALESMAN;7698;30
Linea 3 7521;WARD;SALESMAN;7698;30
Linea 4 7566;JONES;MANAGER;7839;20
Linea 5 7654;MARTIN;SALESMAN;7698;30
Linea 6 7698;BLAKE;MANAGER;7839;30
Linea 7 7782;CLARK;MANAGER;7839;10
Linea 8 7788;SCOTT;ANALYST;7566;20
Linea 9 7839;KING;PRESIDENT;;10
Linea 10 7844;TURNER;SALESMAN;7698;30
Linea 11 7876;ADAMS;CLERK;7788;20
Linea 12 7900;JAMES;CLERK;7698;30
Linea 13 7902;FORD;ANALYST;7566;20
Linea 14 7934;MILLER;CLERK;7782;10

13 comentarios:

  1. Si explicas que es lo que no te funciona, tal vez alguien pueda ayudarte!

    Saludos

    ResponderEliminar
  2. claro y sencillo GRACIAS!!!!!!

    ResponderEliminar
  3. Justo lo que necesitaba. Muchas Gracias ;)

    ResponderEliminar
  4. Compadre usted sabe como transpar datos de unix a sqlplus especificamente ... gracias

    ResponderEliminar
  5. funciona, perfectamente, me gustaria saber si pueden ayudarme, yo necesito hacer esto pero con un procedure que retorna en variables out y me esta dando problemas, mil gracias de ante mano

    ResponderEliminar
  6. Coloca mas detalles como el codigo del procedure, errores y que es lo que quieres hacer.
    Saludos

    ResponderEliminar
  7. gracias por la respuesta, lo que necesito hacer es retornar desde oracle una lista de rutas de archivos .sh que están guardadas en una tabla y con esta lista formar un archivo .sh que ejecute a los que me retorna la lista; entonces con un SP que me retorne esa lista junto a otros datos podria constrirlo desde shell script. mil gracias!!

    ResponderEliminar
  8. Diego, en ese caso no habria mucha diferencia, lo que tu SP tiene que hacer es escribir las lineas por la salida estandar, usando el paquete dbms_output.

    Sigue este ejemplo:

    STORED PROCEDURE:

    CREATE OR REPLACE PROCEDURE show_emp IS
    CURSOR emps IS
    select * from emp;
    BEGIN
    FOR i IN emps LOOP
    dbms_output.put_line(i.empno || ';' || i.ename || ';' || i.job);
    END LOOP;
    END;
    /


    SHELL:
    vi run_emp.sh
    #!/bin/ksh
    resultado=`sqlplus -s 'scott/tiger' << EOF
    set serveroutput on
    set feedback off
    set head off
    set linesize 131
    set pagesize 9999

    exec show_emp;
    exit;
    EOF`

    echo "El resultado es: $resultado"


    PRUEBA:
    oracle@spodemo-01 # ./run_emp.sh
    El resultado es: 7369;SMITH;CLERK
    7499;ALLEN;SALESMAN
    7521;WARD;SALESMAN
    7566;JONES;MANAGER
    7654;MARTIN;SALESMAN
    7698;BLAKE;MANAGER
    7782;CLARK;MANAGER
    7788;SCOTT;ANALYST
    7839;KING;PRESIDENT
    7844;TURNER;SALESMAN
    7876;ADAMS;CLERK
    7900;JAMES;CLERK
    7902;FORD;ANALYST
    7934;MILLER;CLERK

    Espero que te sirva.
    Saludos

    ResponderEliminar
  9. Wow que buena idea, yo al final logre hacerlo con variables de salida pero este ejemplo esta excelente, muchas gracias.... es muy parecido a lo que hice que bueno saber que hay gente dispuesta ayudar mil gracias!!

    ResponderEliminar
  10. Diego, como lo resolviste con variables de salida

    ResponderEliminar
  11. Loco, te pasaste, un saludo desde Argentina.

    ResponderEliminar
  12. Hola Disculpa la demora acabo de ver esto =S lo resolvi asi

    Shell

    set feedback off pages 0
    set lin 5000
    variable outputvar REFCURSOR;
    begin
    PKG_Conciliation2.SP_CONTILIATION_RECARGA ( :outputvar );
    end;
    /
    print :outputvar
    EXIT

    y en el otro Shell
    out1=`sqlplus -s $strconex @/export/home/weblog10/digitel/appsBotonPago/conciliacion/entregables/bancoprepago/mercantil/recibidos/concilia.sql`

    y out1 es una variable del shell que tienen el contenido que me retorna el SP que se corre y con ella puedo operar en el shell script, saludos!!

    ResponderEliminar