few words about your site

plsql

el logo

inicio / programacion / {{page.title}}

{% include toc.html %}

propósito

  • notas sobre pl/sql

objetivos

  • plantilla de paquete, función, y procedimiento con tratamiento de errores
  • una ayuda por pantalla que con el menú genérico muestre una que se llama con el nombre de la pantalla como parámetro de forma que muestra desde base de datos lo que se quiere mostrar en la ayuda
  • ver el fallo del trigger de aud cuando la tabla esta vacia.
  • la parte general del paquete plantilla sacarla a otro paquete por ejemplo utilidades.
  • llevar estos paquetes a un repositorio privado en bitbucket junto con lo de las gráficas
  • entender la inyección de dependencias y ver como aplicarlo a los programas del curro

plantilla de paquete

  • en el esquema de la aplicacion base gab

paquete de proposito general

ref cursor

-- DESDE SQL PLUS
SQL> conn gab/desarrollo@xe
Connected.
SQL> SHOW USER
USER is "GAB"
SQL> VAR RC REFCURSOR;
SQL> exec :rc := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);

PL/SQL procedure successfully completed.

SQL> print rc

GAB_ALUMNO
--------------------------------------------------
GAB_DESCRIPCION
--------------------------------------------------------------------------------
ALUMNO1
DESCRIPCION DEL ALUMNO 1

ALUMNO2
DESCRIPCION DEL ALUMNO 2

ALUMNO3
DESCRIPCION DEL ALUMNO 3

SQL> print rc
SP2-0625: Error printing variable "rc"
SQL>
  • el ref cursor no contiene los datos resultado de la consulta sino que contiene un puntero a la consulta y por eso el segundo print da error
  • el primer print funciona porque SQL Plus mira el cursor de referencia y ve que es un cursor abierto pues la sentencia no lo cierra.
  • Entra en el bucle, obteniendo cada fila de datos de la base de datos, utilizando el cursor de referencia como referencia (puntero) a la consulta relevante, y mostrando cada fila de datos hasta que no tenía más filas para recuperar.
  • Una vez que se han obtenido todas las filas, cierra el cursor.
SQL> ed
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3    begin
  4      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1); -- This returns an open cursor
  5      dbms_output.put_line('Alumnos: '||c_alumnos%ROWCOUNT);
  6      close c_alumnos;
  7*  end;
SQL> /
Alumnos: 0

PL/SQL procedure successfully completed.
  • sale 0 a pesar de que existen 3 alumnos por que c_alumnos%ROWCOONT muestra el número de fila leídas a través del cursos y no hemos leído ninguna.
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3      v_id_alumno  varchar2(50);
  4      v_descripcion varchar2(200);
  5    begin
  6      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);  -- This returns an open cursor
  7      dbms_output.put_line('Pre Fetch: Alumnos: '||c_alumnos%ROWCOUNT);
  8      fetch c_alumnos into v_id_alumno, v_descripcion;
  9      dbms_output.put_line('Post Fetch: Rows: '||c_alumnos%ROWCOUNT);
 10      close c_alumnos;
 11*  end;
SQL> /
Pre Fetch: Alumnos: 0
Post Fetch: Rows: 1

PL/SQL procedure successfully completed.
  • ahora sí que ha leído una fila del cursor.
SQL> ed
Wrote file afiedt.buf
  1  declare
  2      c_alumnos    sys_refcursor;
  3      v_id_alumno  varchar2(50);
  4      v_descripcion varchar2(200);
  5    begin
  6      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);  -- This returns an open cursor
  7      dbms_output.put_line('Pre Fetch: Alumnos: '||c_alumnos%ROWCOUNT);
  8      loop
  9        fetch c_alumnos into v_id_alumno, v_descripcion;
 10        exit when c_alumnos%NOTFOUND;
 11        dbms_output.put_line('Post Fetch: Rows: '||c_alumnos%ROWCOUNT||' '||v_id_alumno||' '||v_descripcion);
 12      end loop;
 13      close c_alumnos;
 14*  end;
SQL> /
Pre Fetch: Alumnos: 0
Post Fetch: Rows: 1 ALUMNO1 DESCRIPCION DEL ALUMNO 1
Post Fetch: Rows: 2 ALUMNO2 DESCRIPCION DEL ALUMNO 2
Post Fetch: Rows: 3 ALUMNO3 DESCRIPCION DEL ALUMNO 3

PL/SQL procedure successfully completed.
-- DESDE EPACF
/* Formatted on 18/12/2017 10:12:13 (QP5 v5.287) */
DECLARE
   RetVal                       EPACF.EPACF_PCK_ACTO_CANDIDATO_VAC.cursorActos;
   P_ID_CURSO                   NUMBER;
   P_ID_ACTO                    NUMBER;
   P_ID_VACANTE                 NUMBER;
   V_DTP_NIF                    VARCHAR2( 200 );
   V_DTP_NOMBRE                 VARCHAR2( 200 );
   V_DTP_APELLIDO1              VARCHAR2( 200 );
   V_DTP_APELLIDO2              VARCHAR2( 200 );
   V_DTP_FECHA_NACIMIENTO       VARCHAR2( 200 );
   V_DTP_TELEFONO               VARCHAR2( 200 );
   V_DTP_EMAIL                  VARCHAR2( 200 );
   V_ACF_ID_COLECTIVO           VARCHAR2( 200 );
   V_ACF_DESCRIPCION            VARCHAR2( 200 );
   V_ACF_TIPO_RESOLUCION        VARCHAR2( 200 );
   V_ACF_DESC_TIPO_RESOLUCION   VARCHAR2( 200 );
   V_DTM_GESTOR                 VARCHAR2( 200 );
   V_DTM_DESC_GESTOR            VARCHAR2( 200 );
   V_ACF_ID_ESTADO              VARCHAR2( 200 );
   V_ACF_DESC_ESTADO            VARCHAR2( 200 );
BEGIN
   P_ID_CURSO := 2017;
   P_ID_ACTO := 1;
   P_ID_VACANTE := 10003;

   RetVal :=
      EPACF.EPACF_PCK_ACTO_CANDIDATO_VAC.F_OBTENER_ACTO_CANDIDATOS_VAC
      (
         P_ID_CURSO,
         P_ID_ACTO,
         P_ID_VACANTE
      );


   DBMS_OUTPUT.PUT_LINE
   (
      'El resultado ' || RETVAL%ROWCOUNT
   );

   LOOP
      FETCH RETVAL
         INTO V_DTP_NIF,
              V_DTP_NOMBRE,
              V_DTP_APELLIDO1,
              V_DTP_APELLIDO2,
              V_DTP_FECHA_NACIMIENTO,
              V_DTP_TELEFONO,
              V_DTP_EMAIL,
              V_ACF_ID_COLECTIVO,
              V_ACF_DESCRIPCION,
              V_ACF_TIPO_RESOLUCION,
              V_ACF_DESC_TIPO_RESOLUCION,
              V_DTM_GESTOR,
              V_DTM_DESC_GESTOR,
              V_ACF_ID_ESTADO,
              V_ACF_DESC_ESTADO;

      EXIT WHEN RETVAL%NOTFOUND;

      DBMS_OUTPUT.PUT_LINE
      (
            'El resultado '
         || RETVAL%ROWCOUNT
         || ' '
         || V_DTP_NIF
         || ' V_DTP_NIF '
         || V_DTP_NOMBRE
         || ' V_DTP_NOMBRE '
         || V_DTP_APELLIDO1
         || ' V_DTP_APELLIDO1 '
         || V_DTP_APELLIDO2
         || ' V_DTP_APELLIDO2 '
         || V_DTP_FECHA_NACIMIENTO
         || ' V_DTP_FECHA_NACIMIENTO '
         || V_DTP_TELEFONO
         || ' V_DTP_TELEFONO '
         || V_DTP_EMAIL
         || ' V_DTP_EMAIL '
         || V_ACF_ID_COLECTIVO
         || ' V_ACF_ID_COLECTIVO '
         || V_ACF_DESCRIPCION
         || ' V_ACF_DESCRIPCION '
         || V_ACF_TIPO_RESOLUCION
         || ' V_ACF_TIPO_RESOLUCION '
         || V_ACF_DESC_TIPO_RESOLUCION
         || ' V_ACF_DESC_TIPO_RESOLUCION '
         || V_DTM_GESTOR
         || ' V_DTM_GESTOR '
         || V_DTM_DESC_GESTOR
         || ' V_DTM_DESC_GESTOR '
         || V_ACF_ID_ESTADO
         || ' V_ACF_ID_ESTADO '
         || V_ACF_DESC_ESTADO
      );
   END LOOP;
--COMMIT;
END;
  • de esta forma se lee el contenido del cursor

    • funciona tanto en SQL como desde el TOAD
  • ver una forma rápida de sacar la información que retorna una función con un refcursor con muchos campos.

    • ¿sacando los campos de la select que crea el cursor? EER * [Error] Execution (2: 1): ORA-06504: PL/SQL: Los tipos de retorno de variables de juego de resultados o la consulta no coinciden ORA-06512: en línea 123

      spool

  • para crearlo desde PL/SQL Developer Archivo / Nuevo / ventana de comandos

  • configuración de spool

  • para configurar una salida y generar por ejemplo un conjunto de inserts

spool c:\inicio\ficheroSpool.sql

SET HEADING OFF
SET FEEDBACK OFF
SET LINESIZE 1000
SET TERMOUT OFF  -- anula la salida por pantalla lo que reduce el tiempo de creación del fichero
PROMPT SET TERMOUT OFF
SELECT 'insert into T_PET  (cod_cue, cod_esp, pos_can, nif, nombre, pos_pet,vac, EST_PET,num_fila) values (''' || P.COD_CUE || ''' , ''' || P.COD_ESP || ''' , ' ||POS_CAN || ' , ''' || P.NIF || ''' , ''' || P.NOMBRE || ''' , ' ||POS_PET || ' , ' || VAC || ' , ' || 0 || ' ,  ' || NUM_FILA || ' );' || DECODE(MOD(ROWNUM, 10), 0, CHR(10) ||  'COMMIT;', NULL)  FROM T_PET P WHERE P.NUM_FILA  < 11 ORDER BY P.NUM_FILA
/
 spool off
  • lleva incluido el commit cada 11 filas
  • el fichero generado lleva la opción de anular la salida por pantalla

  • otras opciones de spool por explorar

SET HEADING OFF
SET FEEDBACK OFF
SET SERVEROUTPUT ON SIZE 1000000
SET PAGESIZE 0
SET FEEDBACK OFF
SET LINESIZE 4096
SET VERIFY OFF
SET TERMOUT OFF
SELECT 1 FROM DUAL;

ejemplo de spool con commit

spool c:\inicio\ficheroSpool.sql

SET HEADING OFF
SET FEEDBACK OFF
SET LINESIZE 1000
SET TERMOUT OFF  -- anula la salida por pantalla lo que reduce el tiempo de creación del fichero
PROMPT SET TERMOUT OFF
SELECT 'insert into EPACT_ACTO_ADJ_VAC_SOL 
 (ACT_ID_CURSO, ACT_ID_ACTO, ACT_ID_VACANTE, ACT_ID_CUERPO, ACT_ID_ESPECIALIDAD, 
 ACT_NIF, ACT_ORDEN, ACT_ID_ESTADO)
values ( 2017, 1,'|| V.N_VACANTE||','''|| V.C_CUERPO||''','''|| V.C_ESPEC||''' , ''' ||FB_DNI_A_NIF(P.DNI)||''' , '||P.N_ORD_PREF ||', '||
'''PENDIENTE'' ); '||
    DECODE(MOD(ROWNUM,10),0,CHR(10)||CHR(13)||'COMMIT;',NULL)
  FROM PETICVAC_AE P, V_VACANTES_AE V
 WHERE P.F_CONV_AE = '27/07/2017'
   AND P.AÑO_INI_CUR = V.AÑO_INI_CUR
   AND P.N_VACANTE = V.N_VACANTE
  AND V.C_ESPEC = 'EF1'
  and rownum < 12 -- para pruebas 
 ORDER BY P.N_ORD_PREF;
 
SELECT 'COMMIT;' FROM DUAL;`

spool off

añadir a un spool

  • para añadir información a un spoll y no borarlo se usa
    • spool <nombre_fichero_spool> append

filas afectadas

  • para saber el número de filas afectadas por una sentencia se usa
    • SQL%ROWCOUNT
CREATE TABLE employees_temp AS SELECT * FROM employees;
BEGIN
  UPDATE employees_temp SET salary = salary * 1.05 WHERE salary < 5000;
  DBMS_OUTPUT.PUT_LINE('Updated ' || SQL%ROWCOUNT || ' salaries.');
END;
  • tiene que estar justo despues de la sentencia.

numero de filas de una consulta

SELECT COD_CUE, COD_ESP, POS_CAN, NIF, NOMBRE, POS_PET, VAC, NIF_ADJ,
             EST_PET, NUM_FILA--, COUNT(*) OVER()

tunning

  • interesante post sobre tunning de consultas

  • Cuando se hace una consulta multi-tabla con //joins//, el orden en que se ponen las tablas en el FROM influye en el plan de ejecución. Aquellas tablas que retornan más filas deben ir en las primeras posiciones, mientras que las tablas con pocas filas deben situarse al final de la lista de tablas.

  • Colocar la tabla que devuelve menor número de registros en el último lugar del FROM.

  • Utilizar siempre que sea posible las mismas consultas. La segunda vez que se ejecuta una consulta, se ahorrará mucho tiempo de parsing y optimización, así que se debe intentar utilizar las mismas consultas repetidas veces.

  • otro post interesante sobre el tema

fiddle

trigger autoincremental

  • cuidado con el trigger para aumentar el contador cuando empezamos con la tabla vacía
SELECT '1 MAX(NUM)'  SENTENCIA , MAX(NUM)  FROM AUD_APL UNION
SELECT '2 NVL(MAX(NUM),0)' , NVL(MAX(NUM),0)  FROM AUD_APL UNION
SELECT '3 NVL(MAX(NUM),0) + 1' ,NVL(MAX(NUM),0) + 1  FROM AUD_APL  UNION
SELECT '4 NVL(NUM,0)'  SENTENCIA , NVL(NUM,0)  FROM AUD_APL UNION
SELECT '5 MAX(NVL(NUM,0))' , MAX(NVL(NUM,0))  FROM AUD_APL UNION
SELECT '6 MAX(NVL(NUM,0)) + 1' , MAX(NVL(NUM,0)) + 1  FROM AUD_APL  
-- DESDE SQL PLUS
SQL> conn gab/desarrollo@xe
Connected.
SQL> SHOW USER
USER is "GAB"
SQL> VAR RC REFCURSOR;
SQL> exec :rc := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);

PL/SQL procedure successfully completed.

SQL> print rc

GAB_ALUMNO
--------------------------------------------------
GAB_DESCRIPCION
--------------------------------------------------------------------------------
ALUMNO1
DESCRIPCION DEL ALUMNO 1

ALUMNO2
DESCRIPCION DEL ALUMNO 2

ALUMNO3
DESCRIPCION DEL ALUMNO 3

SQL> print rc
SP2-0625: Error printing variable "rc"
SQL>
  • el ref cursor no contiene los datos resultado de la consulta sino que contiene un puntero a la consulta y por eso el segundo print da error
  • el primer print funciona porque SQL Plus mira el cursor de referencia y ve que es un cursor abierto pues la sentencia no lo cierra.
  • Entra en el bucle, obteniendo cada fila de datos de la base de datos, utilizando el cursor de referencia como referencia (puntero) a la consulta relevante, y mostrando cada fila de datos hasta que no tenía más filas para recuperar.
  • Una vez que se han obtenido todas las filas, cierra el cursor.
SQL> ed
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3    begin
  4      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1); -- This returns an open cursor
  5      dbms_output.put_line('Alumnos: '||c_alumnos%ROWCOUNT);
  6      close c_alumnos;
  7*  end;
SQL> /
Alumnos: 0

PL/SQL procedure successfully completed.
  • sale 0 a pesar de que existen 3 alumnos por que c_alumnos%ROWCOONT muestra el número de fila leídas a través del cursos y no hemos leído ninguna.
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3      v_id_alumno  varchar2(50);
  4      v_descripcion varchar2(200);
  5    begin
  6      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);  -- This returns an open cursor
  7      dbms_output.put_line('Pre Fetch: Alumnos: '||c_alumnos%ROWCOUNT);
  8      fetch c_alumnos into v_id_alumno, v_descripcion;
  9      dbms_output.put_line('Post Fetch: Rows: '||c_alumnos%ROWCOUNT);
 10      close c_alumnos;
 11*  end;
SQL> /
Pre Fetch: Alumnos: 0
Post Fetch: Rows: 1

PL/SQL procedure successfully completed.
  • ahora sí que ha leído una fila del cursor.
SQL> ed
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3      v_id_alumno  varchar2(50);
  4      v_descripcion varchar2(200);
  5    begin
  6      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);  -- This returns an open cursor
  7      dbms_output.put_line('Pre Fetch: Alumnos: '||c_alumnos%ROWCOUNT);
  8      loop
  9        fetch c_alumnos into v_id_alumno, v_descripcion;
 10        exit when c_alumnos%NOTFOUND;
 11        dbms_output.put_line('Post Fetch: Rows: '||c_alumnos%ROWCOUNT||' '||v_id_alumno||' '||v_descripcion);
 12      end loop;
 13      close c_alumnos;
 14*  end;
SQL> /
Pre Fetch: Alumnos: 0
Post Fetch: Rows: 1 ALUMNO1 DESCRIPCION DEL ALUMNO 1
Post Fetch: Rows: 2 ALUMNO2 DESCRIPCION DEL ALUMNO 2
Post Fetch: Rows: 3 ALUMNO3 DESCRIPCION DEL ALUMNO 3

PL/SQL procedure successfully completed.
  • de esta forma se lee el contenido del cursor

    • funciona tanto en SQL como desde el TOAD|
      SENTENCIA | MAX(NUM) | | :——————-: |:——: |
  • los ref cursores

-- DESDE SQL PLUS
SQL> conn gab/desarrollo@xe
Connected.
SQL> SHOW USER
USER is "GAB"
SQL> VAR RC REFCURSOR;
SQL> exec :rc := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);

PL/SQL procedure successfully completed.

SQL> print rc

GAB_ALUMNO
--------------------------------------------------
GAB_DESCRIPCION
--------------------------------------------------------------------------------
ALUMNO1
DESCRIPCION DEL ALUMNO 1

ALUMNO2
DESCRIPCION DEL ALUMNO 2

ALUMNO3
DESCRIPCION DEL ALUMNO 3

SQL> print rc
SP2-0625: Error printing variable "rc"
SQL>
  • el ref cursor no contiene los datos resultado de la consulta sino que contiene un puntero a la consulta y por eso el segundo print da error
  • el primer print funciona porque SQL Plus mira el cursor de referencia y ve que es un cursor abierto pues la sentencia no lo cierra.
  • Entra en el bucle, obteniendo cada fila de datos de la base de datos, utilizando el cursor de referencia como referencia (puntero) a la consulta relevante, y mostrando cada fila de datos hasta que no tenía más filas para recuperar.
  • Una vez que se han obtenido todas las filas, cierra el cursor.
SQL> ed
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3    begin
  4      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1); -- This returns an open cursor
  5      dbms_output.put_line('Alumnos: '||c_alumnos%ROWCOUNT);
  6      close c_alumnos;
  7*  end;
SQL> /
Alumnos: 0

PL/SQL procedure successfully completed.
  • sale 0 a pesar de que existen 3 alumnos por que c_alumnos%ROWCOONT muestra el número de fila leídas a través del cursos y no hemos leído ninguna.
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3      v_id_alumno  varchar2(50);
  4      v_descripcion varchar2(200);
  5    begin
  6      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);  -- This returns an open cursor
  7      dbms_output.put_line('Pre Fetch: Alumnos: '||c_alumnos%ROWCOUNT);
  8      fetch c_alumnos into v_id_alumno, v_descripcion;
  9      dbms_output.put_line('Post Fetch: Rows: '||c_alumnos%ROWCOUNT);
 10      close c_alumnos;
 11*  end;
SQL> /
Pre Fetch: Alumnos: 0
Post Fetch: Rows: 1

PL/SQL procedure successfully completed.
  • ahora sí que ha leído una fila del cursor.
SQL> ed
Wrote file afiedt.buf

  1  declare
  2      c_alumnos    sys_refcursor;
  3      v_id_alumno  varchar2(50);
  4      v_descripcion varchar2(200);
  5    begin
  6      c_alumnos := gab.gab_pck_alumno.f_buscar_alumnos (null,null,1);  -- This returns an open cursor
  7      dbms_output.put_line('Pre Fetch: Alumnos: '||c_alumnos%ROWCOUNT);
  8      loop
  9        fetch c_alumnos into v_id_alumno, v_descripcion;
 10        exit when c_alumnos%NOTFOUND;
 11        dbms_output.put_line('Post Fetch: Rows: '||c_alumnos%ROWCOUNT||' '||v_id_alumno||' '||v_descripcion);
 12      end loop;
 13      close c_alumnos;
 14*  end;
SQL> /
Pre Fetch: Alumnos: 0
Post Fetch: Rows: 1 ALUMNO1 DESCRIPCION DEL ALUMNO 1
Post Fetch: Rows: 2 ALUMNO2 DESCRIPCION DEL ALUMNO 2
Post Fetch: Rows: 3 ALUMNO3 DESCRIPCION DEL ALUMNO 3

PL/SQL procedure successfully completed.
  • de esta forma se lee el contenido del cursor
    • funciona tanto en SQL como desde el TOAD| 1 MAX(NUM) | | | 2 NVL(MAX(NUM),0) | 0 | | 3 NVL(MAX(NUM),0) + 1 | 1 | | 5 MAX(NVL(NUM,0)) | | | 6 MAX(NVL(NUM,0)) + 1 | |

blogs

por explorar

sqlplus

OWA_UTIL.CALENDARPRINT

  • leo que genera un calendario en HTML
  • uso del paquete owa_util.calendarprint pero me da un error
-- como GAB
SQL> exec OWA_UTIL.CALENDARPRINT(P_QUERY => 'SELECT sysdate ,gab_alumno, null FROM gab_alumno where gab_alumno = ''ALUMNO1'' order by sysdate', P_MF_ONLY =>'Y');
BEGIN OWA_UTIL.CALENDARPRINT(P_QUERY => 'SELECT sysdate ,gab_alumno, null FROM gab_alumno where gab_alumno = ''ALUMNO1'' order by sysdate', P_MF_ONLY =>'Y'); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "SYS.OWA_UTIL", line 2356
ORA-06512: at line 1

SQL> exec OWA_UTIL.CALENDARPRINT(P_QUERY => 'SELECT sysdate ,gab_alumno, null FROM gab_alumno order by gab_alumno', P_MF_ONLY =>'Y');

pl-sql developer

  • para ejecutar la sentencia en la que esta el cursor
    • Tools-preferences-SQL-window

pl_sql_developer_seleccionar_consultas_automaticamente.png