Guia Curso-JAMS-Version Final (Recuperado)

121
Programación en Oracle con PL/SQL - 1 de 121 Programación en Oracle con PL/SQL Apuntes de Clase por: José Andrés Martínez Silva Elaborados en Febrero – Marzo del 2009 Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Transcript of Guia Curso-JAMS-Version Final (Recuperado)

Page 1: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 1 de 121

Programación en Oracle con PL/SQL

Apuntes de Clase

por: José Andrés Martínez Silva

Elaborados en Febrero – Marzo del 2009

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 2: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 2 de 121

Tabla de Contenidos1. Revisión de Conceptos Básicos de SQL................................................................................................3

1.1 Primer ejemplo de programación con PL/SQL: Triggers..............................................................101.2 Consultas sobre la Información Almacenada:...............................................................................111.3 Vistas:............................................................................................................................................13

2. Introducción a PL/SQL........................................................................................................................152.1 Variables de sustitución:................................................................................................................172.2 Tipos de datos soportados en PL/SQL:.........................................................................................212.3 Bloques anidados y control de flujo:.............................................................................................222.4 Funciones: ....................................................................................................................................252.5 Adición de Secuencias a las Tablas:..............................................................................................272.6 Commit, Rollback y Savepoint:....................................................................................................302.7 Revisión Parcial de lo visto hasta este punto:...............................................................................32

3. Procedimientos Almacenados: ............................................................................................................363.1 Bloques de código anidados: ........................................................................................................37

4. Paquetes:..............................................................................................................................................384.1 Empleando CASE para controlar el flujo de un Programa: .........................................................464.2 CASE con condiciones de búsqueda: ...........................................................................................474.3 Control de Iteraciones con LOOP:................................................................................................48

4.3.1 Loop Simple:.........................................................................................................................484.3.2 WHILE LOOPS:....................................................................................................................504.3.3 FOR LOOP:...........................................................................................................................534.3.4 Loops Anidados:....................................................................................................................54

5. Parámetros de Entrada y Salida – Aclaración: ....................................................................................556. Manejo de Excepciones: .....................................................................................................................57

6.1 Excepciones Comunes Incluidas en Oracle:.................................................................................597. Cursores: .............................................................................................................................................62

7.1 Definición de un CURSOR explícito: ..........................................................................................647.1.1 Records: ................................................................................................................................64

7.2 Atributos de los CURSORES: ......................................................................................................677.3 Cursor For Loop: ..........................................................................................................................717.4 Parámetros para los Cursores: ......................................................................................................717.5 Cursores anidados: .......................................................................................................................737.6 For Update: ...................................................................................................................................88

8. De vuelta a los Triggers: .....................................................................................................................918.1 PRAGMA AUTONOMOUS_TRANSACTION: .........................................................................958.2 INSTEAD OF Trigger: .................................................................................................................98

9. Colecciones: ........................................................................................................................................999.1 Tablas PL/SQL: ............................................................................................................................99

9.1.1 Tablas Index By.....................................................................................................................999.1.2 Tablas Anidadas:..................................................................................................................102

9.2 Métodos de las Colecciones: ......................................................................................................1039.3 Tablas de registros: .....................................................................................................................107

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 3: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 3 de 121

1. Revisión de Conceptos Básicos de SQL

Llave primaria: el campo o conjunto de campos que identifica de manera unívoca un registro dentro de una tabla

Llave foránea: el campo o conjunto de campos que permite relacionar una tabla con otra

Indices: análogo al índice del directorio telefónico, es un criterio de ordenamiento que permite encontrar más rápido la información que se busca. En palabras del autor:

“You need to carefully analyze how data will be queried from each table, and then create an appropriate set of indexes. You don’t want to index everything, because that would unnecessarily slow down the process of inserting, updating, and deleting data. That’s why I  said “carefully.” ”

Restricciones: Constraints

UKC: establece un campo o conjunto de campos que no pueden repetirse dentro de una tabla.PKC: establece un campo o conjunto de campos como la llave primaria de una tabla.FKC: establece un campo o conjunto de campos como la llave foránea de una tabla.

Ejercicio 1:

Dado el siguiente modelo escriba el código DDL que se encargará de crearlo en una base de datos Oracle

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 4: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 4 de 121

El ícono   indica que dicho campo o conjunto de campos deben definirse como FKC, el ícono   indica que se trata de un campo que puede tomar el valor NULL, los colores indican 

los campos que se emplean como llaves foráneas.

Solución Propuesta:

-- script de creacion de las tablas del modelo de la 2da clase del curso CREATE TABLE alumno( id_alumno number not null, documento_alumno varchar2(20) not null, nombre_alumno varchar2(50) not null, apellido_alumno varchar2(50) not null );

CREATE TABLE leccion( id_leccion number not null, fecha date not null );

CREATE TABLE examen( id_examen number not null, descripcion_examen varchar2(50) not null );

CREATE TABLE tema( id_tema number not null, nombre_tema varchar2(50) not null, descripcion_tema varchar2(200), id_leccion number not null );

CREATE TABLE examen_leccion( id_examen number not null, id_leccion number not null );

CREATE TABLE examen_alumno( id_examen number not null, id_alumno number not null, fecha date not null, calificacion number );

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 5: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 5 de 121

Ejercicio 2:

Escriba el código DDL que incluirá las restricciones necesarias para definir las llaves primarias y foráneas

Solución Propuesta:

-- restricciones para cada una de las tablas --llaves primarias PKC -- ALTER TABLE <table_name> ADD -- CONSTRAINT <constraint_name> -- PRIMARY KEY ( -- <column_name_1>, -- <column_name_2>,... -- <column_name_N> );

ALTER TABLE alumno ADD CONSTRAINT alumno_pkc PRIMARY KEY( id_alumno );

ALTER TABLE leccion ADD CONSTRAINT leccion_pkc PRIMARY KEY( id_leccion );

ALTER TABLE examen ADD CONSTRAINT examen_pkc PRIMARY KEY( id_examen );

ALTER TABLE tema ADD CONSTRAINT tema_pkc PRIMARY KEY( id_tema );

ALTER TABLE examen_alumno ADD CONSTRAINT examen_alumno_pkc PRIMARY KEY(

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 6: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 6 de 121

id_examen, id_alumno );

ALTER TABLE examen_leccion ADD CONSTRAINT examen_leccion_pkc PRIMARY KEY( id_examen, id_leccion );

--llaves foraneas FKC -- ALTER TABLE <table_name> ADD -- CONSTRAINT <constraint_name> -- FOREIGN KEY ( -- <column_name_1>, -- <column_name_2>,... -- <column_name_N> ) -- REFERENCES <referenced_table_name> ( -- <column_name_1>, -- <column_name_2>,... -- <column_name_N> );

ALTER TABLE tema ADD CONSTRAINT tema_fkc FOREIGN KEY( id_leccion) REFERENCES leccion( id_leccion);

Ejercicio 3:

Escriba el código DDL que incluirá los índices en las tablas creadas anteriormente, previamente se debe decidir que campos conviene definir como índices basándose en la idea de que un índice se crea sobre el campo o los campos que serán consultados.

Tabla alumno: es probable que las consultas se realicen por el código o por el apellido, por lo que podría ser una buena idea crear dos índices para esta tabla. No parece ser una buena idea definir un índice sobre la llave primaria, pues las consultas no se realizarán sobre esta columna. Adicionalmente dicho índice ya existe.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 7: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 7 de 121

Solución Propuesta:

-- indices para las tablas -- CREATE [UNIQUE] INDEX <index_name> -- on <table_name> ( -- <column_name_1>, -- <column_name_2>, -- <column_name_N> );

CREATE UNIQUE INDEX alumno_ik1 on alumno( documento_alumno);

CREATE INDEX alumno_ik2 on alumno( apellido_alumno);

CREATE INDEX leccion_ik2 on leccion( fecha);

CREATE INDEX tema_ik1 on tema( nombre_tema);

Inserte ahora los siguientes datos en las tablas:

id_alumno documento_alumno nombre_alumno apellido_alumno

1 79799331 Jose Martinez

2 53783975 Paola Hernandez

id_leccion fecha

1 17­Feb­09

2 18­Feb­09

id_examen descripcion_examen

1 Primera revision

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 8: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 8 de 121

id_tema nombre_tema descripcion_tema id_leccion

1 presentacion 1

2 Modelamiento DB 1

id_examen id_leccion

1 1

id_examen id_alumno fecha calificacion

1 1 18­Feb­2009 4

1 2 18­Feb­2009 5

Código sugerido (DDL):

/*INSERT INTO <table_name> ( <column_name_1>, <column_name_2>, ... <column_name_N> ) VALUES ( <column_value_1>, <column_value_2>,... <column_value_N> );*/

INSERT INTO alumno( id_alumno, documento_alumno, nombre_alumno, apellido_alumno ) VALUES(1, '79799331', 'Jose', 'Martinez' );

INSERT INTO alumno( id_alumno, documento_alumno, nombre_alumno, apellido_alumno )

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 9: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 9 de 121

VALUES(2, '53783975', 'Paola', 'Hernandez' );

INSERT INTO LECCION( id_leccion, fecha) VALUES(1, '17-Feb-09' );

INSERT INTO LECCION( id_leccion, fecha) VALUES(2, '18-Feb-09' );

INSERT INTO EXAMEN( id_examen, descripcion_examen) VALUES(1, 'Primera Revision' );

INSERT INTO TEMA( id_tema, nombre_tema, descripcion_tema, id_leccion ) VALUES(1, 'presentacion', NULL, 1 );

INSERT INTO TEMA( id_tema, nombre_tema, descripcion_tema, id_leccion

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 10: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 10 de 121

) VALUES(2, 'Modelamiento DB', NULL, 1 );

INSERT INTO examen_leccion( id_examen, id_leccion) VALUES(1, 1 );

INSERT INTO examen_alumno( id_examen, id_alumno, fecha, calificacion) VALUES(1, 1, '18-Feb-09', 4 );

INSERT INTO examen_alumno( id_examen, id_alumno, fecha, calificacion) VALUES(1, 2, '18-Feb-09', 5 );

1.1 Primer ejemplo de programación con PL/SQL: Triggers

Los Triggers son acciones (programas) que se ejecutan de forma automática cuando una acción se realiza – o va a realizarse – sobre una tabla (entidad)

Requisito: Insertar información en la tabla mediante la instrucción INSERT

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 11: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 11 de 121

Ejercicio 4:

Escriba un Trigger que evite que un alumno reciba la calificación de 0 en un examen y solicite al profesor que generosamente la convierta en un 1.

Solución Propuesta:

CREATE OR REPLACE TRIGGER benice_trg BEFORE INSERT ON examen_alumno FOR EACH ROW BEGIN IF :new.calificacion < 1 THEN raise_application_error(-20000, 'Sr. Instructor recuerde que la calificacion minima admitida es 1'); END IF; END;

Ingrese ahora los siguiente registros empleando los códigos DDL correspondientes:

id_alumno documento_alumno nombre_alumno apellido_alumno

3 989056789 Juan Perez

id_examen id_alumno fecha calificacion

1 3 18­Feb­09 0

¿Cuáles son los códigos? 

INSERT INTO alumno( id_alumno, documento_alumno, nombre_alumno, apellido_alumno ) VALUES(3, '989056789', 'Juan', 'Perez' );

INSERT INTO examen_alumno(

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 12: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 12 de 121

id_examen, id_alumno, fecha, calificacion) VALUES(1, 3, '18-Feb-09', 0 );

¿Qué ocurre cuando se intenta insertar el segundo registro?

1.2 Consultas sobre la Información Almacenada:

SELECT: se emplea la instrucción SELECT para recuperar información de una tabla o un conjunto de tablas.

Consultas básicas de ejemplo:

Recuperar las calificaciones obtenidas por un alumno específico a lo largo del curso:

SELECT * FROM EXAMEN_ALUMNO WHERE ID_ALUMNO = 1

Seleccionarlas usando el documento no el ID:

SELECT * FROM EXAMEN_ALUMNO, ALUMNO WHERE EXAMEN_ALUMNO.ID_ALUMNO = ALUMNO.ID_ALUMNO AND ALUMNO.DOCUMENTO_ALUMNO LIKE '53783975'

Otra forma de lograr el mismo resultado:

SELECT * FROM EXAMEN_ALUMNO JOIN ALUMNO ON EXAMEN_ALUMNO.ID_ALUMNO = ALUMNO.ID_ALUMNO WHERE ALUMNO.DOCUMENTO_ALUMNO LIKE '53783975'

Pero cuál es la ventaja de emplear la segunda forma? 

Modifique el código SQL anterior para lograr un primer “reporte”:

SELECT * FROM EXAMEN_ALUMNO JOIN ALUMNO ON EXAMEN_ALUMNO.ID_ALUMNO = ALUMNO.ID_ALUMNO

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 13: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 13 de 121

Debería obtener algo como lo siguiente:

Y si en lugar de la consulta anterior utiliza el siguiente código SQL:

SELECT EA.ID_EXAMEN, EA.FECHA, EA.CALIFICACION, A.DOCUMENTO_ALUMNO, A.NOMBRE_ALUMNO, A.APELLIDO_ALUMNOFROM EXAMEN_ALUMNO EA JOINALUMNO AONEA.ID_ALUMNO = A.ID_ALUMNO;

Verá un reporte más adecuado:

Reporte sugerido en clase:

SELECT A.APELLIDO_ALUMNO, A.NOMBRE_ALUMNO, E.DESCRIPCION_EXAMEN, EA.CALIFICACION FROM ALUMNO A JOIN EXAMEN_ALUMNO EA ON A.ID_ALUMNO = EA.ID_ALUMNO JOIN EXAMEN E ON E.ID_EXAMEN = EA.ID_EXAMEN;

Y esta consulta se podría convertir en una Vista, que de acuerdo con las palabras del autor del libro guía1 puede definirse como:

1 El material que se empleó inicialmente como guía en este curso corresponde al libro: Beginning PL/SQL: From Novice to Professional escrito por Don Bales. Sitio web: http://www.apress.com/book/view/9781590598825

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 14: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 14 de 121

1.3 Vistas:

“A view represents the definition of a SQL query (SELECT statement) as though it were just  another table in the database. Hence, you can INSERT into and UPDATE, DELETE, and SELECT from a view just as you can any table...”

La forma de definir una vista es la siguiente:

CREATE [OR REPLACE] VIEW <view_name> AS <sql_select_statement>; 

De acuerdo con lo cual la vista deseada puede definirse de la siguiente manera:

CREATE OR REPLACE VIEW vista_reporte1 ASSELECT EA.ID_EXAMEN, EA.FECHA, EA.CALIFICACION, A.DOCUMENTO_ALUMNO, A.NOMBRE_ALUMNO, A.APELLIDO_ALUMNOFROM EXAMEN_ALUMNO EA JOINALUMNO AONEA.ID_ALUMNO = A.ID_ALUMNO;

UPDATE: esta instrucción se emplea cuando se desea actualizar un registro existente en una tabla. Su forma general es la siguiente:

UPDATE <table_name> SET    <column_name_1> = <column_value_1>,         <column_name_2> = <column_value_2>,...         <column_name_N> = <column_value_N>; 

Actualizar la calificación del alumno Juan Perez(id 3) en el examen presentado el 18­Feb­09 (id 1):

UPDATE EXAMEN_ALUMNOSET CALIFICACION = 3WHERE ID_ALUMNO = 3 AND ID_EXAMEN = 1;

Luego de ejecutar esta consulta puede consultarse la vista creada anteriormente con el fin de ver reflejados los cambios:

SELECT * FROM VISTA_REPORTE1;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 15: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 15 de 121

Ejercicio 5:

Escriba el código SQL que permita actualiza la calificación de Juan Perez en el examen presentado el 18­Feb­09 para el caso en que no se conoce el ID del alumno pero si su DOCUMENTO:

UPDATE EXAMEN_ALUMNOSET CALIFICACION = 3.5WHERE ID_ALUMNO = (SELECT ID_ALUMNO FROM ALUMNO WHERE DOCUMENTO_ALUMNO LIKE '989056789') AND ID_EXAMEN = 1;

La sección del código que se encuentra resaltada se conoce como un SUBQUERY. 

Cuando se desea eliminar un registro existente en una tabla se emplea la instrucción DELETE.

Nota al pie: cuando se desee eliminar una constraint previamente definida se puede emplear la siguiente instrucción:

ALTER TABLE <nombre_tabla>DROP CONSTRAINT <nombre_constraint>

Ejercicio 6:

Insertar un nuevo alumno con los siguientes datos: 4,11111,Alumno,Eliminado

INSERT INTO ALUMNOVALUES(4,'11111','Alumno','Eliminado');

Dicho alumno se puede eliminar con la instrucción:

DELETE FROM ALUMNO WHERE ID_ALUMNO = 4;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 16: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 16 de 121

2. Introducción a PL/SQL

PL/SQL es un lenguaje de procedimientos que extiende las funcionalidades del lenguaje SQL.

Un programa escrito en PL/SQL combina bloques de código que – generalmente – cumplen con una tarea específica. Existen dos clases de bloques, aquellos que llevan nombre y los bloques anónimos. Los bloques con nombre generalmente se pueden almacenar en la base de datos y ser invocados luego para cumplir con la tarea para la que fueron escritos. Los bloques anónimos por su parte sólo existen al momento de su ejecución y no pueden ser llamados luego desde otros programas o bloques.

La estructura fundamental de un bloque PL/SQL es la siguiente:

DECLARE    Sentencias de declaraciónBEGIN    Sentencias de ejecuciónEXCEPTION    Sentencias para el manejo de excepcionesEND; 

Únicamente son obligatorias las Sentencias de ejecución.

Ejemplo 1:

Construya un bloque PL/SQL que extraiga el nombre y el apellido del alumno cuyo id = 1

BEGINSELECT NOMBRE_ALUMNO, APELLIDO_ALUMNOFROM ALUMNOWHERE ID_ALUMNO = 1END;

Suponga ahora que queremos mostrar en pantalla la información extraída y realizar sobre la misma algún tipo de operación. 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 17: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 17 de 121

Ejemplo 2:

DECLAREaux_nombre varchar2(50);aux_apellido varchar2(50);BEGINSELECT NOMBRE_ALUMNO, APELLIDO_ALUMNOINTO aux_nombre, aux_apellidoFROM ALUMNOWHERE ID_ALUMNO = 1;DBMS_OUTPUT.PUT_LINE ('Estudiante seleccionado: '||aux_nombre||' '||aux_apellido); END;/

DBMS_OUTPUT.PUT_LINE is a call to the procedure PUT_LINE. This procedure is a part of  the DBMS_OUTPUT package that is owned by the Oracle user SYS. 

El caracter / en la última línea es requerido pues es el responsable de que el código sea ejecutado.

Ejemplo 3:

DECLAREaux_nombre varchar2(50);aux_apellido varchar2(50);BEGINSELECT NOMBRE_ALUMNO, APELLIDO_ALUMNOINTO aux_nombre, aux_apellidoFROM ALUMNOWHERE ID_ALUMNO = 1;DBMS_OUTPUT.PUT_LINE ('Estudiante seleccionado (en altas): '||upper(aux_nombre)||' '||upper(aux_apellido)); END;/

Suponga ahora que se ingresa un id_alumno que no se encuentra en la base de datos, en este caso no habrá información que mostrar y es – muy – recomendable manejar la excepción que se presenta.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 18: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 18 de 121

Ejemplo 4:

DECLAREaux_nombre varchar2(50);aux_apellido varchar2(50);BEGINSELECT NOMBRE_ALUMNO, APELLIDO_ALUMNOINTO aux_nombre, aux_apellidoFROM ALUMNOWHERE ID_ALUMNO = 12;DBMS_OUTPUT.PUT_LINE ('Estudiante seleccionado (en altas): '||upper(aux_nombre)||' '||upper(aux_apellido)); EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE ('No existe un estudiante con el id: 12 '); END;/

2.1 Variables de sustitución:

Una primera forma de “obtener” información por parte del usuario para luego utilizarla dentro de un bloque o programa PL/SQL es empleando variables de sustitución.

Ejercicio 1:

Escriba en PL/SQL un bloque que presente la mundialmente famosa frase: Hola Mundo en mayúsculas

DECLAREaux_hola varchar2(50) := 'Hola Mundo';BEGINDBMS_OUTPUT.PUT_LINE(upper(aux_hola));END;/

Ejercicio 2:

Modifique el código anterior para que en lugar de Hola Mundo, el usuario vea una línea de salida con su nombre. Ejemplo: si el usuario ingresa el nombre Jose, la salida del programa será Hola Jose – en mayúsculas ­

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 19: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 19 de 121

DECLAREaux_hola varchar2(50) := 'Hola';aux_nombre varchar2(50) := '&nombre_usuario';BEGINDBMS_OUTPUT.put_line(upper(aux_hola)||' '||upper(aux_nombre));END;./

En el caso anterior &nombre_usuario es una variable de sustitución, su valor será solicitado al usuario cuando se ejecute el bloque PL/SQL y el valor ingresado será empleado en las Sentencias de Ejecución.

SQL*Plus: para poder trabajar con el bloque anterior es necesario hacer uso de la consola SQL*Plus, un cliente en modo texto que provee Oracle2 para interactuar con la base de datos.

La primera instrucción que debe ejecutarse tan pronto como se lanza el cliente es:connect system/password@localhost con lo que se establece la conexión al servidor de bases de datos. Una vez conectado al servidor escriba el código anterior y observe el resultado de la ejecución:

SQL> Enter value for nombre_usuario: jose andres old 3: aux_nombre varchar2(50) := '&nombre_usuario'; new 3: aux_nombre varchar2(50) := 'jose andres';

PL/SQL procedure successfully completed.

Observe que en ningún momento aparece la cadena deseada, si este es su caso modifique el bloque PL/SQL añadiendo la siguiente línea: SET SERVEROUTPUT ON; al comienzo del mismo

SET SERVEROUTPUT ON; DECLAREaux_hola varchar2(50) := 'Hola';aux_nombre varchar2(50) := '&nombre_usuario';BEGINDBMS_OUTPUT.put_line(upper(aux_hola)||' '||upper(aux_nombre));END;.

2 Los ejemplos y ejercicios expuestos en este documento han sido probados con la versión Express de Oracle 10g, la cual se encuentra disponible sin costo en la siguiente dirección: http://www.oracle.com/technology/software/products/database/xe/index.html

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 20: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 20 de 121

/

Nota alguna diferencia?

By default, SQL*PLUS doesn't read what a PL/SQL program has written with dbms_output.  With set serveroutput on, this behaviour is changed.

Ejercicio 1:

Escriba un programa PL/SQL que solicite al usuario el documento de un alumno, consulte su información en la base de datos y presente su nombre y apellido junto con la calificación obtenida en el examen cuyo id es 1. En caso de que el documento ingresado no coincida con la información de ningún estudiante debe lanzarse una excepción que informe al usuario de la no existencia de datos para ese documento.

SET SERVEROUTPUT ON;DECLARE documento varchar2(20) := '&documento_ingresado'; id_examen number := 1; aux_nombre varchar2(50); aux_apellido varchar2(50); aux_calificacion number; BEGIN SELECT A.NOMBRE_ALUMNO, A.APELLIDO_ALUMNO, EA.CALIFICACION INTO aux_nombre, aux_apellido, aux_calificacion FROM ALUMNO A JOIN EXAMEN_ALUMNO EA ON A.ID_ALUMNO = EA.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE documento; DBMS_OUTPUT.PUT_LINE('Informacion Solicitada: '||aux_nombre||' '||aux_apellido||' '||aux_calificacion); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('No existe informacion para el documento: '||documento); END; . /

Para evitar la salida en pantalla del valor original y el valor sustituido se modifica el código de la siguiente manera:

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 21: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 21 de 121

SET VERIFY OFF; SET SERVEROUTPUT ON ;DECLARE documento varchar2(20) := '&documento_ingresado'; id_examen number := 1; aux_nombre varchar2(50); aux_apellido varchar2(50); aux_calificacion number; BEGIN SELECT A.NOMBRE_ALUMNO, A.APELLIDO_ALUMNO, EA.CALIFICACION INTO aux_nombre, aux_apellido, aux_calificacion FROM ALUMNO A JOIN EXAMEN_ALUMNO EA ON A.ID_ALUMNO = EA.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE documento; DBMS_OUTPUT.PUT_LINE('Informacion Solicitada: '||aux_nombre||' '||aux_apellido||' '||aux_calificacion); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('No existe informacion para el documento: '||documento); END; . /

El símbolo . Indica el final de un bloque PL/SQL empleando SQL*Plus

Pregunta tipo Certificación:

Qué ocurre al ejecutar el siguiente código:

SET SERVEROUTPUT ON; DECLARE v_var1 NUMBER(2) := 123; v_var2 NUMBER(3) := 123; v_var3 NUMBER(5,3) := 123456.123; BEGIN DBMS_OUTPUT.PUT_LINE('v_var1: '||v_var1); DBMS_OUTPUT.PUT_LINE('v_var2: '||v_var2); DBMS_OUTPUT.PUT_LINE('v_var3: '||v_var3); END;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 22: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 22 de 121

Respuesta: La declaración de v_var1 y v_var3 especifican tamaños menores a los valores que reciben al momento de su inicialización, se lanza un error al momento de su compilación.

Resulta interesante definir los tipos de las variables como objetos existentes en la base de datos, de esta manera se evita el tener que modificar el código PL/SQL cuando un cambio se realiza sobre la base de datos. Empleando el mismo modelo de base de datos que se ha venido utilizando hasta este punto considere las tablas alumno y examen_alumno:

El código del ejercicio planteado puede reescribirse de la siguiente manera:

SET SERVEROUTPUT ON;

DECLARE documento ALUMNO.DOCUMENTO_ALUMNO%TYPE := '&documento_ingresado'; id_examen EXAMEN_ALUMNO.ID_EXAMEN%TYPE := 1; aux_nombre ALUMNO.NOMBRE_ALUMNO%TYPE; aux_apellido ALUMNO.APELLIDO_ALUMNO%TYPE; aux_calificacion EXAMEN_ALUMNO.CALIFICACION%TYPE;

BEGIN SELECT A.NOMBRE_ALUMNO, A.APELLIDO_ALUMNO, EA.CALIFICACION INTO aux_nombre, aux_apellido, aux_calificacion FROM ALUMNO A JOIN EXAMEN_ALUMNO EA ON A.ID_ALUMNO = EA.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE documento; DBMS_OUTPUT.PUT_LINE('Informacion Solicitada: '||aux_nombre||' '||aux_apellido||' '||aux_calificacion);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 23: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 23 de 121

EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('No existe informacion para el documento: '||documento);

END; . /

Este cambio en el tipo de las variables utilizadas, garantizará que si en un futuro se cambia por ejemplo el tamaño o el tipo de dato del campo para NOMBRE_ALUMNO el programa PL/SQL siga funcionando sin necesidad de modificarlo en absoluto.

2.2 Tipos de datos soportados en PL/SQL:

Declaración Capacidad Máxima Observación

VARCHAR2 (tamaño) 4000 bytes

CHAR [(tamaño)] 2000 bytes Si no se define un tamaño el valor por defecto es de 1byte (1 caracter)

NUMBER [(#dígitos, precisión)]

DATE

TIMESTAMP Almacena la fecha + la hora con una precisión de 9 dígitos

BOOLEAN TRUE/FALSE/NULL

LONG Extiende el VARCHAR2 permitiendo almacenar hasta 2GB

LOB Large Object → 4 GB de almacenamiento. Se emplea para el almacenamiento de objetos binarios como Videos, Imágenes, Audios, etc...

When you’re a programmer, it is important to know the operators that you can use in a programming language. They determine your various options for solving a programmatic problem. The following are the operators you can use in PL/SQL: 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 24: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 24 de 121

● Arithmetic (** , * , / , + , –) ● Comparison (=, <>, !=, <, >, <=, >=, LIKE, IN, BETWEEN, IS NULL, IS NOT NULL, 

NOT IN) ● Logical (AND, OR, NOT) ● String (||, LIKE) ● Expressions ● Operator precedence ● ** , NOT ● +, – (arithmetic identity and negation) *, /, +, –, || =, <>, !=, <= ● >=, <, >, LIKE, BETWEEN, IN, IS NULL ● AND (logical conjunction) ● OR (logical inclusion) 

2.3 Bloques anidados y control de flujo:

Para realizar los siguientes ejercicios es necesario ingresar los siguientes datos en las tablas correspondientes:

LECCION:

id_leccion Fecha

3 19­Feb­09

EXAMEN

id_examen descripcion_examen

2 2do examen

EXAMEN_LECCION:

id_examen id_leccion

2 2

2 3

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 25: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 25 de 121

EXAMEN_ALUMNO:

id_examen id_alumno fecha calificacion

2 1 23­Feb­09 2

2 2 23­Feb­09 1.5

2 3 23­Feb­09 2.5

Ejercicio:

Defina el código DDL necesario para la inserción de los anteriores datos en las tablas correspondientes y ejecútelo empleando la consola de SQL*Plus.

Nota al pie: al estar trabajando en la consola SQL*Plus el autocommit se encuentra por  defecto desactivado, para almacenar los datos deseados debe modificarse esta condición mediante la instrucción SET AUTOCOMMIT ON;

The changes to the database that have been executed by a single application session are not actually “saved” to the database until a COMMIT occurs. Work within a transaction up to and just before the commit can be rolled back; after a commit has been issued, work within that transaction cannot be rolled back. 

Note that those SQL statements should be either committed or rejected as a group. 

Es posible emplear el SELECT con algunos operadores adicionales que proveen resultados interesantes:

SELECT SUM(campo) permite obtener el resultado de sumar los contenidos de un determinado campo. Así por ejemplo si se desea obtener la suma de todas las calificaciones almacenadas en la tabla EXAMEN_ALUMNO puede ejecutarse el siguiente código:

SELECT SUM(calificacion) FROM EXAMEN_ALUMNO;

SELECT COUNT(campo) permite determinar el número total de registros que cumplan con un criterio, por ejemplo para determinar el número de calificaciones disponibles para un alumno en particular puede ejecutarse el siguiente código SQL:

SELECT COUNT(ID_EXAMEN) FROM EXAMEN_ALUMNO WHERE ID_ALUMNO = 2;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 26: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 26 de 121

Ejercicio:

Escriba un programa en PL/SQL que permita – basado en las calificaciones obtenidas en los exámenes 1 y 2 – determinar si un alumno se encuentra aprobando el curso (calificación promedio mayor o igual a 3) o si por el contrario corre riesgo de reprobarlo (calificación promedio inferior a 3). Emplee IF CONDICION THEN para controlar el flujo de la aplicación basado en las consultas realizadas.

Solución Propuesta:

--23 de Febrero - 09 SET SERVEROUTPUT ON; DECLARE aux_documento ALUMNO.DOCUMENTO_ALUMNO%TYPE := '&documento_ingresado'; aux_nombre ALUMNO.NOMBRE_ALUMNO%TYPE; aux_apellido ALUMNO.APELLIDO_ALUMNO%TYPE; aux_sumatoria EXAMEN_ALUMNO.CALIFICACION%TYPE; aux_numero_examenes NUMBER;

BEGIN --se recuperan y se suman las calificaciones disponibles para el documento definido SELECT SUM(EA.CALIFICACION) INTO aux_sumatoria FROM EXAMEN_ALUMNO EA JOIN ALUMNO A ON EA.ID_ALUMNO = A.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE aux_documento; --se determina el numero de examenes presentados por ese alumno en particular SELECT COUNT(EA.ID_EXAMEN) INTO aux_numero_examenes FROM EXAMEN_ALUMNO EA JOIN ALUMNO A ON EA.ID_ALUMNO = A.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE aux_documento; /*se divide la sumatoria de las calificaciones entre el numero de examenes y se evalua frente a la nota minima para aprobar: 3.0*/ IF((aux_sumatoria/aux_numero_examenes) >= 3.0) THEN DBMS_OUTPUT.PUT_LINE('EL ALUMNO VA APROBANDO EL CURSO HASTA EL MOMENTO');

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 27: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 27 de 121

ELSIF (aux_numero_examenes > 0 AND (aux_sumatoria/aux_numero_examenes) >= 3.0) THEN DBMS_OUTPUT.PUT_LINE('EL ALUMNO VA REPROBANDO EL CURSO HASTA EL MOMENTO'); ELSE --ocurre cuando no se encuentran examenes presentados por ese alumno DBMS_OUTPUT.PUT_LINE('NO EXISTEN REGISTROS PARA EL DOCUMENTO: '||aux_documento); END IF;

EXCEPTION --observe como nunca se llega a esta excepcion WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('NO EXISTEN REGISTROS PARA EL DOCUMENTO: '||aux_documento);

END; . /

2.4 Funciones: 

El ejercicio anterior puede definirse como una función y almacenarse en la base de datos, para ser ejecutada luego:

CREATE [OR REPLACE] FUNCTION <function_name> [( <parameter_name_1>           [IN] [OUT] <parameter_data_type_1>, <parameter_name_2>           [IN] [OUT] <parameter_data_type_2>,... <parameter_name_N>           [IN] [OUT] <parameter_data_type_N> )] RETURN                                  <return_data_type> IS   ­­the declaration section BEGIN   ­­ the executable section   return <return_data_type>; EXCEPTION   ­­ the exception­handling section END; / 

Aplicando esta sintaxis al ejercicio anterior:

--23 de Febrero - 09 - version funcion CREATE OR REPLACE FUNCTION get_basic_info(documento IN VARCHAR2) RETURN VARCHAR2

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 28: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 28 de 121

IS

--asignando el parametro de entrada aux_documento ALUMNO.DOCUMENTO_ALUMNO%TYPE := documento;aux_nombre ALUMNO.NOMBRE_ALUMNO%TYPE; aux_apellido ALUMNO.APELLIDO_ALUMNO%TYPE; aux_sumatoria EXAMEN_ALUMNO.CALIFICACION%TYPE; aux_numero_examenes NUMBER;

BEGIN --se recuperan y se suman las calificaciones disponibles para el --documento definido SELECT SUM(EA.CALIFICACION) INTO aux_sumatoria FROM EXAMEN_ALUMNO EA JOIN ALUMNO A ON EA.ID_ALUMNO = A.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE aux_documento; --se determina el numero de examenes presentados por ese alumno en --particular SELECT COUNT(EA.ID_EXAMEN) INTO aux_numero_examenes FROM EXAMEN_ALUMNO EA JOIN ALUMNO A ON EA.ID_ALUMNO = A.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE aux_documento; /*se divide la sumatoria de las calificaciones entre el numero de examenes y se evalua frente a la nota minima para aprobar: 3.0*/ IF((aux_sumatoria/aux_numero_examenes) >= 3.0) THEN RETURN ('EL ALUMNO VA APROBANDO EL CURSO HASTA EL MOMENTO'); ELSIF (aux_numero_examenes > 0 AND (aux_sumatoria/aux_numero_examenes) >= 3.0) THEN RETURN ('EL ALUMNO VA REPROBANDO EL CURSO HASTA EL MOMENTO'); ELSE --ocurre cuando no se encuentran examenes presentados por ese alumno RETURN ('NO EXISTEN REGISTROS PARA EL DOCUMENTO: '||aux_documento); END IF;

EXCEPTION --observe como nunca se llega a esta excepcion WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('NO EXISTEN REGISTROS PARA EL DOCUMENTO: '||aux_documento);

END; /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 29: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 29 de 121

Unlike most compilers, which will display a listing of errors found in source code, Oracle  stores any errors it finds in a database table named USER_ERRORS. If you want to see the  specific details, and you may well, you need to retrieve the error listing yourself. Use the  SQL*Plus command SHOW ERRORS

Para verificar si la función anterior funciona correctamente puede ejecutar la siguiente línea en la consola SQL* Plus: SELECT get_basic_info('79799330') FROM DUAL;

Have you noticed that I’m using a table by the name of dual in the conditional INSERT . . .  SELECT statement? dual is a table owned by the Oracle database (owner SYS) that has one column and one row. It is very handy, because anytime you select against this table, you get  one, and only one, row back. ...See how using dual to test how a SQL function might work can be handy? It allows you to  hack away without any huge commitment in code. 

Ejercicio:

Escriba una función en PL/SQL que reciba como parámetro una cadena de texto y devuelva a la salida el siguiente mensaje: Usted ingreso el texto: cadena_ingresada, en donde cadena_ingresada corresponde a la entrada del usuario al momento de llamar la función.

Solución Propuesta:

CREATE OR REPLACE FUNCTION say_hello(cadena IN VARCHAR2) RETURN VARCHAR2 IS mensaje VARCHAR2(50) := 'Usted ingreso el texto: '; BEGIN mensaje := mensaje || cadena; RETURN mensaje; END;

Unidad de Prueba (Test Unit):

select say_hello('hola amigos') from dual;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 30: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 30 de 121

2.5 Adición de Secuencias a las Tablas:

Hasta el momento cada vez que se desea insertar un registro en la tabla ALUMNO es necesario especificar el valor para la columna ID_ALUMNO, pero en realidad ese valor debería ser determinado por la base de datos de forma automática. Para modificar el comportamiento de la tabla e insertar los registros sin tener que ocuparse del último id empleado se creará una secuencia:

An Oracle sequence is an Oracle database object that can be used to generate unique numbers. You can use sequences to automatically generate primary key values. 

CREATE SEQUENCE sequence_name    MINVALUE value    MAXVALUE value    START WITH value    INCREMENT BY value    CACHE value;

MINVALUE: valor mínimo de la secuencia, para el caso de la tabla que se está presentando será necesario especificar el siguiente valor al último id definido manualmente, así por ejemplo, si su tabla cuenta con la siguiente información:

El valor del parámetro MINVALUE debe ser 4.

MAXVALUE: define – si así se desea – el valor máximo que podrá tomar la secuencia. En caso de no especificar ningún valor para este parámetro su valor por defecto es de 999999999999999999999999999

START WITH: es el primer valor que se asigna a la secuencia, si su valor no se especifica el mismo coincide con el MINVALUE

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 31: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 31 de 121

INCREMENT BY: especifica cuál es el incremento que ocurre entre un valor y el siguiente dentro de la secuencia.

CACHE: este parámetro – opcional – sirve para almacenar en memoria un número dado de valores – siguientes – para aumentar la velocidad de respuesta. Por ejemplo si se especifica algo como CACHE 20, el primer valor de la secuencia será consultado a la base de datos, pero los 20 siguientes estarán disponibles en una memoria intermedia de acceso más rápido. Sin embargo la desventaja de usar este parámetro es que si por algún motivo se produce una fallo en el sistema, los valores previamente almacenados en el CACHE se perderán y la secuencia quedará con un espacio vacío en la mitad (GAP).

Expuesto lo anterior puede definirse la sentencia para la tabla ALUMNO de la siguiente manera:

CREATE SEQUENCE seq_alumno MINVALUE 4 START WITH 4 INCREMENT BY 1 NOCACHE

Y ahora, para insertar un par de nuevos registros en la tabla, se emplean los siguientes comandos SQL:

INSERT INTO ALUMNO VALUES (seq_alumno.NEXTVAL,'19900897','LUIS','SILVA');

INSERT INTO ALUMNO VALUES (seq_alumno.NEXTVAL,'45904827','CAMILA','CARDENAS');

Ejercicio:

Cree las secuencias que permitan insertar nuevos registros en la tabla lección, examen y tema.

CREATE SEQUENCE seq_leccionMINVALUE 4 START WITH 4 INCREMENT BY 1 NOCACHE

CREATE SEQUENCE seq_examen MINVALUE 2

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 32: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 32 de 121

START WITH 2INCREMENT BY 1 NOCACHE

CREATE SEQUENCE seq_temaMINVALUE 3START WITH 3INCREMENT BY 1 NOCACHE

Ejercicio:

Defina una nueva función en PL/SQL llamada create_alumno que reciba como parámetros el documento, nombre y apellido de un alumno, cree el registro correspondiente en la tabla ALUMNO y retorne un valor de TRUE si la operación se pudo realizar o FALSE en caso contrario.

Solución Propuesta:

CREATE OR REPLACE FUNCTION create_alumno(documento IN VARCHAR2, nombre IN VARCHAR2, apellido IN VARCHAR2) RETURN BOOLEAN IS

BEGIN INSERT INTO ALUMNO VALUES (seq_alumno.NEXTVAL,documento,nombre,apellido); RETURN TRUE;

EXCEPTION WHEN OTHERS THEN RETURN FALSE;--se produjo un error END;

Ejercicio: 

Escriba el código PL/SQL que le permita probar el funcionamiento de la función create_alumno

Solución Propuesta:

DECLARE estado BOOLEAN:=create_alumno('123144609','Carlos','Peralta');

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 33: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 33 de 121

BEGIN IF(estado) THEN DBMS_OUTPUT.PUT_LINE('Alumno creado'); ELSE DBMS_OUTPUT.PUT_LINE('Ocurrio un error en la creacion del alumno'); END IF; END; . /

2.6 Commit, Rollback y Savepoint: 

Aunque se ha mencionado en un párrafo anterior el uso de SET AUTOCOMMIT ON como una alternativa para obviar el comportamiento transaccional de Oracle, en realidad este comportamiento debe integrarse a los programas elaborados en PL/SQL

La sintaxis básica de estos tres comandos es la siguiente:

COMMIT [WORK]: hace permanentes los cambios definidos en una transacción, el parámetro WORK es opcional

ROLLBACK [WORK]: deshace los cambios que se habían definido en la transacción anterior, el parámetro WORK es opcional

SAVEPOINT NAME: define un punto a partir del cual se pueden deshacer los cambios con el comando ROLLBACK, cualquier cambio que se haya definido antes del SAVEPOINT no se ve afectado por el ROLLBACK.

Ejemplo 1:

Dada la tabla lección con el siguiente contenido:

ID_LECCION FECHA ­­­­­­­­­­ ­­­­­­­­­­­­­­­­­­ 

 1 17­FEB­09  2 18­FEB­09  3 19­FEB­09 

Inserte un registro para la fecha 23­FEB­09 – recuerde que sus tablas cuentan ahora con una secuencia para la generación automática de la llave primaria:

INSERT INTO leccion

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 34: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 34 de 121

VALUES (seq_leccion.NEXTVAL, '23-FEB-09') 2 ;

Defina un punto de guardado SAVEPOINT A;

Inserte un nuevo registro para la fecha 24­Feb­09 y luego deshaga los últimos cambios empleando: ROLLBACK TO A;

Qué información tiene la tabla leccion?:

select * from leccion;

ID_LECCION FECHA ---------- ------------------

1 17-FEB-09 2 18-FEB-09 3 19-FEB-09 4 23-FEB-09

Cierre su sesión con SQL*Plus y vuelva a iniciarla ¿qué datos contiene ahora la tabla leccion?. 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 35: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 35 de 121

2.7 Revisión Parcial de lo visto hasta este punto:

(El siguiente ejercicio ha sido tomado del libro “PL/SQL By Example de Benjamin Rosenzweig y Elena Silvestrova Rakhimov ”)

1. Create a table called CHAP4 with two columns; one is ID (a number) and the other is NAME, which is a VARCHAR2(20). 

2. Create a sequence called CHAP4_SEQ that increments by units of 5.3. Write a PL/SQL block that does the following, in this order:

1. Declares two variables: one for v_name and one for v_id. The v_name variable can be used throughout the block to hold the name that will be inserted; realize that the value will change in the course of the block. 

2. The block inserts into the table the name of the student who is enrolled in the most classes3 and uses a sequence for the ID. Afterward there is SAVEPOINT A. 

3. The student with the fewest classes4 is inserted. Afterward there is SAVEPOINT B.4. The instructor who is teaching the most courses5 is inserted in the same way. 

Afterward there is SAVEPOINT C. 5. Using a SELECT INTO statement, hold the value of the instructor in the variable 

v_id.6. Undo the instructor insertion by using rollback.7. Insert the instructor teaching the fewest courses6, but do not use the sequence to 

generate the ID. Instead, use the value from the first instructor, whom you have since undone. 

8. Insert the instructor teaching the most courses, and use the sequence to populate his or her ID. 

4. Add DBMS_OUTPUT throughout the block to display the values of the variables as they change. (This is good practice for debugging.) 

3 John Doe4 Marcus Indigus5 Roy Barnes6 Louis Mint

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 36: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 36 de 121

Solución Propuesta Puntos 1 y 2:

-- Create a table called CHAP4 with two columns; -- one is ID (a number) and the other is NAME, -- which is a VARCHAR2(20). DROP TABLE CHAP4;

CREATE TABLE CHAP4( ID NUMBER, NAME VARCHAR(20));

-- constraints for this table ALTER TABLE CHAP4 ADD CONSTRAINT CHAP4_PK PRIMARY KEY(ID);

-- Create a sequence called CHAP4_SEQ that increments by units of 5. DROP SEQUENCE CHAP4_SEQ;

CREATE SEQUENCE CHAP4_SEQ MINVALUE 1 START WITH 1 INCREMENT BY 5;

Usted puede guardar este archivo de texto e invocarlo desde la consola de SQL*Plus empleando una sintaxis similar a la siguiente (por favor tenga en cuenta la estructura de archivos de su sistema operativo):

@/home/jamslug/revisionParcial01.sql

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 37: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 37 de 121

Solución Propuesta Punto 3:

SET SERVEROUTPUT ON;

DECLARE v_name CHAP4.NAME%TYPE; v_id CHAP4.ID%TYPE; BEGIN v_name := 'John Doe';--the student who is enrolled in the most classes DBMS_OUTPUT.PUT_LINE(v_name);

INSERT INTO CHAP4 VALUES(CHAP4_SEQ.NEXTVAL,v_name); SAVEPOINT A; v_name := 'Marcus Indigus';--the student with the fewest classes DBMS_OUTPUT.PUT_LINE(v_name); INSERT INTO CHAP4 VALUES(CHAP4_SEQ.NEXTVAL,v_name); SAVEPOINT B; v_name := 'Roy Barnes';--the instructor who is teaching the most courses DBMS_OUTPUT.PUT_LINE(v_name); INSERT INTO CHAP4 VALUES(CHAP4_SEQ.NEXTVAL,v_name); SAVEPOINT C; SELECT ID INTO v_id FROM CHAP4 WHERE NAME LIKE 'Roy Barnes'; ROLLBACK TO B; v_name := 'Louis Mint';--the instructor teaching the fewest courses

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 38: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 38 de 121

DBMS_OUTPUT.PUT_LINE(v_id||' '||v_name); -- do not use the sequence to generate the ID. Instead, use the value -- from the first instructor, whom you have since undone. INSERT INTO CHAP4 VALUES(v_id,v_name); COMMIT; v_name := 'Roy Barnes';--the instructor who is teaching the most courses DBMS_OUTPUT.PUT_LINE(v_name); INSERT INTO CHAP4 VALUES(CHAP4_SEQ.NEXTVAL,v_name); COMMIT; END; . /

Usted puede guardar este archivo de texto e invocarlo desde la consola de SQL*Plus empleando una sintaxis similar a la siguiente (por favor tenga en cuenta la estructura de archivos de su sistema operativo):

@/home/jamslug/revisionParcial02.sql

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 39: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 39 de 121

3. Procedimientos Almacenados: 

De manera similar a como se definen funciones para su almacenamiento y posterior re utilización en PL/SQL pueden definirse procedimientos. La diferencia fundamental entre una función y un procedimiento es que este último no RETORNA ningún valor. La sintaxis general para definir un procedimiento es la siguiente:

CREATE [OR REPLACE] PROCEDURE <procedure_name> [( <parameter_name_1> [IN] [OUT] <parameter_data_type_1>, <parameter_name_2> [IN] [OUT] <parameter_data_type_2>,... <parameter_name_N> [IN] [OUT] <parameter_data_type_N> )] IS --the declaration section BEGIN -- the executable section EXCEPTION -- the exception-handling section END; /

Ejemplo 1: Now you’ll create a procedure that wraps the SYS.DBMS_OUTPUT.put_line() procedure, but uses a very short name. You’ll end up using the SYS.DBMS_OUTPUT.put_line() procedure a lot. It gets tiresome to type a 24­character method name every time you want to display a line of text on the screen in SQL*Plus. So, to save keystrokes, you will give your SYS.DBMS_OUTPUT.put_line() wrapper procedure the name pl(), as in p for put and l for line. ­ tomado del libro guía ­

CREATE OR REPLACE PROCEDURE pl(cadena IN VARCHAR2) IS BEGIN DBMS_OUTPUT.PUT_LINE(cadena); END; . /

--TESTING UNIT DECLARE v_aux VARCHAR2(500) := 'Hola alias para la funcion de impresion de linea'; BEGIN pl(v_aux); pl('Este es el primer procedimiento almacenado'); pl(123); END

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 40: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 40 de 121

3.1 Bloques de código anidados: 

Usted puede anidar bloques de PL/SQL dentro de otros bloques de PL/SQL, gracias a lo cual puede construir programas más complejos manejando condiciones particulares en cada uno de los bloques. Considere el ejemplo en el que se consulta, empleando el documento de un alumno, si éste se encuentra aprobando o reprobando un curso.

--24 de Febrero - 09 - version funcion con bloques anidados CREATE OR REPLACE FUNCTION get_basic_info(documento IN VARCHAR2) RETURN VARCHAR2 IS

aux_documento ALUMNO.DOCUMENTO_ALUMNO%TYPE := documento;--asignando el parametro de entrada aux_nombre ALUMNO.NOMBRE_ALUMNO%TYPE; aux_apellido ALUMNO.APELLIDO_ALUMNO%TYPE; aux_sumatoria EXAMEN_ALUMNO.CALIFICACION%TYPE; aux_numero_examenes NUMBER;

BEGIN --se recuperan y se suman las calificaciones disponibles para el documento definido --primer bloque BEGIN SELECT SUM(EA.CALIFICACION) INTO aux_sumatoria FROM EXAMEN_ALUMNO EA JOIN ALUMNO A ON EA.ID_ALUMNO = A.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE aux_documento; --manejo de una condicion no valida que no es una EXCEPTION de ORACLE IF(aux_sumatoria=0) THEN RETURN('NO EXISTEN CALIFICACIONES PARA EL DOCUMENTO'); END IF; END;

--se determina el numero de examenes presentados por ese alumno en particular --segundo bloque BEGIN SELECT COUNT(EA.ID_EXAMEN) INTO aux_numero_examenes FROM EXAMEN_ALUMNO EA JOIN ALUMNO A

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 41: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 41 de 121

ON EA.ID_ALUMNO = A.ID_ALUMNO WHERE A.DOCUMENTO_ALUMNO LIKE aux_documento; --manejo de una condicion no valida que no es una EXCEPTION de ORACLE IF(aux_numero_examenes=0) THEN RETURN('NO EXISTEN EXAMENES PARA EL DOCUMENTO'); END IF; END;

--tercer bloque BEGIN /*se divide la sumatoria de las calificaciones entre el numero de examenes y se evalua frente a la nota minima para aprobar: 3.0*/ IF((aux_sumatoria/aux_numero_examenes) >= 3.0) THEN RETURN('EL ALUMNO VA APROBANDO EL CURSO HASTA EL MOMENTO'); ELSE RETURN('EL ALUMNO VA REPROBANDO EL CURSO HASTA EL MOMENTO'); END IF; END; END; . /

--TEST UNIT SELECT get_basic_info('79799330') FROM DUAL;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 42: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 42 de 121

4. Paquetes:

Un paquete es un grupo de funciones y procedimientos que se emplean para realizar tareas comunes. Puede pensar en ellos como el equivalente a las librerías en otros lenguajes de programación. Un paquete está compuesto por dos partes, su especificación y su cuerpo. 

Especificación del Paquete: corresponde a la interfaz pública del paquete, la que ofrece las funciones disponibles para ser empleadas desde otros programas PL/SQL.

Cuerpo del Paquete: corresponde a la lógica detrás de cada una de las funciones y demás recursos que la descripción anuncia al público.

Ejemplo de Paquete:

CREATE OR REPLACE PACKAGE DATES AS /* dates.pks by Donald J. Bales on 12/15/2006 Additional DATE data type methods. */

-- The maximum and minimum date values. d_MAX constant7 date := to_date8('99991231235959', 'YYYYMMDDHH24MISS'); d_MIN constant date := to_date('-47120101', 'S9YYYYMMDD');

7 CONSTANT se emplea para definir una constante, las cuales al contrario de las variables no sufren modificaciones en su valor asignado durante la ejecución de un programa PL/SQL

8 to_date(cadena, formato): esta función devuelve una fecha correspondiente a la cadena indicada y con el formato especificado. (más información en: http://www.techonthenet.com/oracle/functions/to_date.php). Si se desea obtener una fecha y una hora puede emplearse la función to_timestamp(cadena, formato)

9 Observe la diferencia entre usar la S y no usarla en el formato especificado:

SQL> select to_date('-47120101','SYYYYMMDD') from dual; TO_DATE('-47120101 ------------------ 01-JAN-12

SQL> select to_date('-47120101','YYYYMMDD') from dual; select to_date('-47120101','YYYYMMDD') from dual * ERROR at line 1: ORA-01841: (full) year must be between -4713 and +9999, and not be 0

El parámetro S utiliza el siguiente año al año especificado, en este caso -4713 en lugar de -4712

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 43: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 43 de 121

-- Returns the specified date with the time set to 23:59:59, therefore, -- the end of the day. FUNCTION end_of_day( aid_date in date ) return date; -- Returns constant d_MAX. This is useful in SQL statements where the -- constant DATES.d_MAX is not accessible.

FUNCTION get_max return date;

-- Returns constant d_MIN. This is useful in SQL statements where the -- constant DATES.d_MIN is not accessible.

FUNCTION get_min return date;

-- Text-based help for this package. "set serveroutput on" in SQL*Plus.

PROCEDURE help;

-- Returns a randomly generated date that exists between the years specified.

FUNCTION random( ain_starting_year in number, ain_ending_year in number ) return date;

-- Returns the specified date with the time set to 00:00:00, therefore, the -- start of the day.

FUNCTION start_of_day( aid_date in date ) return date;

-- Test unit for this package.

PROCEDURE test;

end DATES; /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 44: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 44 de 121

@/home/jamslug/date-spec.sql

El cuerpo de este paquete es el lugar en el cual se definen cada uno de los recursos expuestos mediante su descripción:

CREATE OR REPLACE PACKAGE BODY DATES AS /* dates.pkb by Donald J. Bales on 12/15/2006 Additional DATE data type methods */

FUNCTION end_of_day( aid_date in date ) return date is

begin return to_date(to_char(aid_date, 'SYYYYMMDD')10||'235959', 'SYYYYMMDDHH24MISS'); end end_of_day;

FUNCTION get_max return date is

begin return d_MAX; end get_max;

FUNCTION get_min return date is

begin return d_MIN; end get_min;

FUNCTION random( ain_starting_year in number, ain_ending_year in number )

10 to_char(entrada, formato) convierte a una cadena de caracteres la entrada aplicando el formato especificado. En este caso puntual la entrada es una fecha (DATE). Esta función también puede emplearse para convertir tipos de dato NUMBER en cadenas de caracteres

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 45: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 45 de 121

return date is

d_random date; n_day number; n_month number; n_year number;

begin n_year := round11(DBMS_RANDOM.value12( ain_starting_year, ain_ending_year), 0); loop13 n_month := round(DBMS_RANDOM.value(1, 12), 0); n_day := round(DBMS_RANDOM.value(1, 31), 0); begin d_random := to_date(lpad14(to_char(n_year), 4, '0')|| lpad(to_char(n_month), 2, '0')|| lpad(to_char(n_day), 2, '0'), 'YYYYMMDD'); EXIT15; exception when OTHERS then

11 round(numero, lugares decimales): redondea el número de entrada al número de lugares decimales indicado. En este caso devolverá un valor entero sin decimales.

12 DBMS_RANDOM es un paquete . La función value(n1, n2) definida dentro de ese paquete devuelve un valor aleatorio comprendido entre los límites especificado. Encuentra más información sobre este paquete en: http://www.psoug.org/reference/dbms_random.html

13 Se emplea la instrucción loop cuando se desea definir un ciclo pero se desconoce el número de veces que éste se debe ejecutar y se quiere asegurar que al menos se ejecute una vez (más información en: http://www.techonthenet.com/oracle/loops/gen_loop.php). El ciclo termina cuando se encuentra la instrucción EXIT o cuando se evalúa como cierta una condición EXIT WHEN

14 lpad(cadena, posiciones a rellenar, caracter de relleno): devuelve la cadena con las posiciones a rellenar al comienzo de la misma – a su izquierda – ocupadas por el caracter de relleno. El siguiente ejemplo ilustra esta idea:

select lpad(to_char(2009),4,'0') from dual;

LPAD(TO_CHAR ------------ 2009

select lpad(to_char(09),4,'0') from dual;

LPAD(TO_CHAR ------------ 000915 Si ninguna excepción ha ocurrido el Loop termina en este punto

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 46: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 46 de 121

if SQLCODE16 <> -183917 then pl(SQLERRM18); end if; end; end loop; return d_random; end random;

FUNCTION start_of_day( aid_date in date ) return date is

begin return trunc19(aid_date); end start_of_day;

-- Write up the help text here in this help method PROCEDURE help is

begin pl20('============================== PACKAGE =============================='); pl(chr21(9));

16 SQLCODE es una función que devuelve el valor numérico asociado a la última excepción que se haya generado. Puede emplear el siguiente enlace para obtener mas información sobre los diferentes valores de error que pueden ocurrir: http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/rzala/rzalaco.htm

17 Se refiere al valor númerico de una excepción que se produce cuando una fecha no se encuentra en el rango válido (January 1, 4712 BC to December 31, 9999 AD)

18 SQLERRM es una función que devuelve el mensaje de error asociado a la última excepción que se haya generado19 trunc(date, format): devuelve la fecha truncada de acuerdo con el formato especificado:

select trunc(to_date('19770217050000','YYYYMMDDHHMISS')) FROM DUAL; TRUNC(TO_DATE('197 ------------------ 17-FEB-77

select trunc(to_date('19770217','YYYYMMDD'),'Q') from dual; TRUNC(TO_DATE('197 ------------------ 01-JAN-77

20 pl es un procedimiento almacenado que sirve como envoltorio (wrapper) a la función PUT_LINE21 chr(numero) devuelve el carácter asociado al valor de número. Para el caso de chr(9) devuelve un tabulador horizontal

(TAB). Si lo desea puede consultar la lista de códigos ASCII en la siguiente página: http://www.techonthenet.com/ascii/chart.php

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 47: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 47 de 121

pl('DATES'); pl(chr(9)); pl('------------------------------ CONSTANTS ----------------------------'); pl(chr(9)); pl('d_MAX'); pl(chr(9)||'Represents the maximum value for the DATE data type.'); pl('d_MIN'); pl(chr(9)||'Represents the minimum value for the DATE data type.'); pl(chr(9)); pl('------------------------------ FUNCTIONS ----------------------------'); pl(chr(9)); pl('DATES.end_of_day('); pl('aid_date in date)'); pl('return date;'); pl(chr(9)||'Returns the passed date with the time portion set to the end '); pl(chr(9)||'of the day:'); pl(chr(9)||'23:59:59 (HH24:MI:SS).'); pl(chr(9)); pl('DATES.get_max( )'); pl('return date;'); pl(chr(9)||'Returns the constant DATES.d_MAX.'); pl(chr(9)); pl('DATES.get_min( )'); pl('return date;'); pl(chr(9)||'Returns the constant DATES.d_MIN.'); pl(chr(9)); pl('DATES.random('); pl('ain_starting_year in number,'); pl('ain_ending_year in number)'); pl('return date;'); pl(chr(9)||'Returns a random date that exists between the specified years.'); pl(chr(9)); pl('DATES.start_of_day('); pl('aid_date in date)'); pl('return date;'); pl(chr(9)||'Returns the passed date with the time portion set to the start'); pl(chr(9)||'of the day:'); pl(chr(9)||'00:00:00 (HH24:MI:SS).'); pl(chr(9));

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 48: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 48 de 121

pl('------------------------------ PROCEDURES ----------------------------'); pl(chr(9)); pl('DATES.help( );'); pl(chr(9)||'Displays this help text if set serveroutput is on.'); pl(chr(9)); pl('DATES.test( );'); pl(chr(9)||'Built-in test unit. It will report success or error for each'); pl(chr(9)||'test if set'); pl(chr(9)||'serveroutput is on.'); pl(chr(9)); end help;

PROCEDURE test is

d_date date;

begin pl('============================== PACKAGE ==============================='); pl(chr(9)); pl('DATES');

pl(chr(9)); pl('1. Testing constants d_MIN and d_MAX'); if d_MIN < d_MAX then pl('SUCCESS'); else pl('ERROR: d_MIN is not less than d_MAX'); end if;

pl('2. Testing end_of_day()'); if to_char(end_of_day(SYSDATE), 'HH24MISS') = '235959' then pl('SUCCESS'); else pl('ERROR: end_of_day is not 23:59:59'); end if;

pl('3. Testing get_max()'); if get_max() = d_MAX then pl('SUCCESS'); else

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 49: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 49 de 121

pl('ERROR: get_max() is not equal to d_MAX'); end if;

pl('4. Testing get_min()'); if get_min() = d_MIN then pl('SUCCESS'); else pl('ERROR: get_min() is not equal to d_MIN'); end if;

pl('5. Testing random() 1000 times'); for i in 1..1000 loop 22

d_date := random(1, 9999); end loop; pl('SUCCESS');

pl('6. Testing start_of_day()'); if to_char(start_of_day(SYSDATE), 'HH24MISS') = '000000' then pl('SUCCESS'); else pl('ERROR: start_of_day is not 00:00:00'); end if; end test;

end DATES; /

@/home/jamslug/date-body.sql

Ejercicio: compile los códigos de la especificación y el cuerpo del paquete, luego escriba un bloque anónimo en PL/SQL que permita ejecutar el procedimiento test.

Solución Propuesta:

begin DATES.test; end; .

22 For Loop permite definir un ciclo con un número predeterminado de ejecuciones, para este caso puntual se está definiendo un ciclo que se ejecutará 1000 veces. La sintaxis general de esta instrucción es:

FOR loop_counter IN [REVERSE] lowest_number..highest_numberLOOP {.statements.}END LOOP;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 50: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 50 de 121

/

4.1 Empleando CASE para controlar el flujo de un Programa: 

Ya se ha visto hasta esta parte la forma de controlar el flujo de una aplicación empleando las instrucciones IF THEN, IF ELSE THEN, e IF THEN ELSIF THEN ELSE. También es posible controlar el flujo de un programa empleando la instrucción CASE.

La estructura básica de una sentencia CASE es la siguiente:

CASE SELECTOR    WHEN EXPRESSION 1 THEN STATEMENT 1;    WHEN EXPRESSION 2 THEN STATEMENT 2;    ...    WHEN EXPRESSION N THEN STATEMENT N;    ELSE STATEMENT N+1; END CASE; 

Ejercicio 1: haciendo uso de las instrucciones IF ELSE THEN escriba una función que reciba un número y determine si se trata de un número par o impar. Usted puede saber si un número es par si al dividirlo por dos el residuo de la operación es 0, en Oracle usted puede emplear la función MOD(m,n) para determinar el residuo de dividir m entre n

CREATE OR REPLACE FUNCTION par_impar(v_numero IN NUMBER) RETURN VARCHAR2 IS

BEGIN IF(v_numero>0) THEN IF23(MOD(v_numero,2)!=0) THEN RETURN 'impar'; ELSE RETURN 'par'; END IF; ELSE RETURN 'esta funcion solo sirve con enteros mayores a 0'; END IF;

END; . /

23 Observe como este IF se encuentra dentro de un primer IF, a esto se le conoce con el nombre de IF anidados

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 51: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 51 de 121

@/home/jamslug/par_o_impar_1.sql

Unidad de Prueba (Test Unit) para el ejemplo anterior:

SELECT par_impar(4) FROM dual;

1. Qué ocurre si ingresa 5 en lugar de 4?2. Qué ocurre si ingresa 0 o un número negativo?

Ejercicio 2: reescriba la función anterior empleando CASE en lugar de IF ELSE THEN

CREATE OR REPLACE FUNCTION par_impar(v_numero IN NUMBER) RETURN VARCHAR2 IS

residuo NUMBER := MOD(v_numero,2); BEGIN CASE residuo WHEN 0 THEN RETURN 'par'; ELSE RETURN 'impar'; END CASE;

END; . /@/home/jamslug/par_o_impar_2.sql

Qué unidad de prueba puede emplear para verificar esta función?

4.2 CASE con condiciones de búsqueda: 

Es posible también emplear el case sobre búsquedas que devuelven resultados booleanos: TRUE, FALSE, NULL. En este caso la sintaxis a emplear es la siguiente:

CASE    WHEN SEARCH CONDITION 1 THEN STATEMENT 1;    WHEN SEARCH CONDITION 2 THEN STATEMENT 2;    ...    WHEN SEARCH CONDITION N THEN STATEMENT N; 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 52: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 52 de 121

   ELSE STATEMENT N+1; END CASE; 

Ejercicio 3: reescriba la función anterior empleando un CASE con condiciones de búsqueda.

CREATE OR REPLACE FUNCTION par_impar(v_numero IN NUMBER) RETURN VARCHAR2 IS

BEGIN CASE WHEN (MOD(v_numero,2)=0) THEN --si devuelve TRUE RETURN 'par'; ELSE RETURN 'impar'; END CASE;

END; . /

4.3 Control de Iteraciones con LOOP: 

En ocasiones se deseea que un programa realice varias veces la misma tarea, para lograr esto deben definirse LOOPS. En Oracle hay cuatro tipos diferentes de LOOPS, se expondrán a continuación los 3 primeros.

4.3.1 Loop Simple: en principio el LOOP mas simple que se puede declarar tiene esta forma:

LOOP STATEMENT 1; STATEMENT 2; ... STATEMENT N; END LOOP;

Sin embargo, definido de esta manera, el LOOP no acabaría de iterar jamás. Es necesario incluir una condición que indique la salida del LOOP:

LOOP STATEMENT 1; STATEMENT 2; IF CONDITION THEN

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 53: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 53 de 121

EXIT; END IF; END LOOP;

Ejemplo 1: escriba un bloque anónimo PL/SQL que realice las siguientes tareas1. construya una tabla denominada Prueba con las columnas ID (NUMBER) y 

ALEATORIO (NUMBER)2. defina una secuencia seq_prueba que comience en 1 y se incrementa en 13. defina un LOOP simple que inserte 100 registros en la tabla, emplea la secuencia 

creada en el punto 2 para obtener el ID de cada nuevo registro. El valor aleatorio por otra parte, lo obtendrá con la función value(n1,n2) del paquete DBMS_RANDOM

Solución Propuesta:

DROP TABLE PRUEBA; CREATE TABLE PRUEBA( ID NUMBER, ALEATORIO NUMBER);

ALTER TABLE PRUEBA ADD CONSTRAINT PRUEBA_PK PRIMARY KEY(ID);

DROP SEQUENCE SEQ_PRUEBA; CREATE SEQUENCE SEQ_PRUEBA MINVALUE 1 START WITH 1 INCREMENT BY 1;

DECLARE v_n1 CONSTANT NUMBER := 1; v_n2 CONSTANT NUMBER := 250; v_aux NUMBER; BEGIN v_aux := 0; LOOP INSERT INTO PRUEBA VALUES(SEQ_PRUEBA.NEXTVAL,DBMS_RANDOM.value(v_n1, v_n2)); v_aux := v_aux+1; IF(v_aux = 100) THEN

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 54: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 54 de 121

EXIT; END IF; END LOOP; END; . /   El mismo ejemplo se podría modificar ligeramente para emplear la sintaxis de EXIT WHEN en lugar de EXIT de la siguiente manera:

DROP TABLE PRUEBA; CREATE TABLE PRUEBA( ID NUMBER, ALEATORIO NUMBER);

ALTER TABLE PRUEBA ADD CONSTRAINT PRUEBA_PK PRIMARY KEY(ID);

DROP SEQUENCE SEQ_PRUEBA; CREATE SEQUENCE SEQ_PRUEBA MINVALUE 1 START WITH 1 INCREMENT BY 1;

DECLARE v_n1 CONSTANT NUMBER := 1; v_n2 CONSTANT NUMBER := 250; v_aux NUMBER; BEGIN v_aux := 0; LOOP INSERT INTO PRUEBA VALUES(SEQ_PRUEBA.NEXTVAL,DBMS_RANDOM.value(v_n1, v_n2)); v_aux := v_aux+1; EXIT WHEN (v_aux = 100); END LOOP; END; .

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 55: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 55 de 121

/   4.3.2 WHILE LOOPS: la segunda clase de LOOPS disponibles se denomina WHILE, su estructura básica es la siguiente:

    WHILE CONDITION LOOP        STATEMENT 1;        STATEMENT 2;        ...        STATEMENT N;     END LOOP; 

La diferencia fundamental con los LOOPS simples es que la condición para continuar iterando se evalúa antes de comenzar la iteración.

Es posible incluir las condiciones EXIT y EXIT WHEN en los WHILE LOOP con el fin de introducir salidas prematuras en el mismo:

WHILE TEST_CONDITION LOOP    STATEMENT 1;    STATEMENT 2;    IF EXIT_CONDITION THEN       EXIT;    END IF; END LOOP; 

Ejemplo 1: escriba un bloque de código PL/SQL que realice las siguientes tareas:1. construya una tabla denominada Prueba con las columnas ID y ALEATORIO2. defina una secuencia seq_prueba que comience en 1 y se incrementa en 13. defina un LOOP simple que inserte 100 registros en la tabla, emplea la secuencia 

creada en el punto 2 para obtener el ID de cada nuevo registro. El valor aleatorio por otra parte, lo obtendrá con la función value(n1,n2) del paquete DBMS_RANDOM. Esta vez insertará únicamente valores enteros, empleando para tal fin la función ROUND(número, precisión). Si en algún momento se inserta un número que ya había sido insertado antes se termina prematuramente la ejecución del código.

Solución Propuesta:

DROP TABLE PRUEBA; CREATE TABLE PRUEBA( ID NUMBER,

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 56: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 56 de 121

ALEATORIO NUMBER);

ALTER TABLE PRUEBA ADD CONSTRAINT PRUEBA_PK PRIMARY KEY(ID);

DROP SEQUENCE SEQ_PRUEBA; CREATE SEQUENCE SEQ_PRUEBA MINVALUE 1 START WITH 1 INCREMENT BY 1;

DECLARE v_n1 CONSTANT NUMBER := 1; v_n2 CONSTANT NUMBER := 250; v_aux NUMBER; v_verify1 NUMBER; v_verify2 NUMBER; BEGIN v_aux := 0; WHILE (v_aux < 100) LOOP v_verify1 := round(DBMS_RANDOM.value(v_n1, v_n2),0); INSERT INTO PRUEBA VALUES(SEQ_PRUEBA.NEXTVAL,v_verify1); v_aux := v_aux+1; SELECT COUNT(ID) INTO v_verify2 FROM PRUEBA WHERE ALEATORIO = v_verify1; EXIT WHEN (v_verify2 > 1); END LOOP; END; . /

Ejercicio 1: escriba un procedimiento PL/SQL que calcule la suma de los números del 1 al 10 y vaya mostrando a cada paso el valor de la suma parcial. A manera de ejemplo se presentan las primeras líneas que debe mostrar en pantalla este procedimiento:13

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 57: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 57 de 121

61015...

Solución Propuesta:

CREATE OR REPLACE PROCEDURE sumar1_10 IS var_sumatoria NUMBER := 0; var_contador NUMBER := 0; BEGIN WHILE(var_contador < 10) LOOP var_contador := var_contador + 1; var_sumatoria := var_sumatoria + var_contador; pl(var_sumatoria); END LOOP; END; . /

Ejercicio 2: escriba la unidad de prueba para el procedimiento sumar1_10

Solución Propuesta:

begin sumar1_10; end; ./

4.3.3 FOR LOOP: esta tercera clase de LOOP se denominan numéricos pues requieren un valor de este tipo para su terminación. La estructura básica de los mismos es la siguiente:

FOR loop_counter IN [REVERSE] lower_limit..upper_limit LOOP    STATEMENT 1;    STATEMENT 2;    ...    STATEMENT N; END LOOP; 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 58: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 58 de 121

Ejemplo 1: escriba una unidad de prueba para la función – previamente creada – par_impar que haciendo uso de un FOR LOOP realice 100 pruebas aleatorias de la función e imprima sus resultados en pantalla.

Solución Propuesta:

DECLARE v_aux NUMBER; BEGIN FOR i IN 1..100 LOOP v_aux := round(DBMS_RANDOM.value(1,1000),0); pl(v_aux||':'||par_impar(v_aux)); END LOOP; END; . /@/home/jamslug/unit_test_for_loop.sql

There is no need to define the loop counter in the declaration section of the PL/SQL block.  This variable is defined by the loop construct. lower_limit and upper_limit are two integer  numbers or expressions that evaluate to integer values at runtime, and the double dot (..)  serves as the range operator. 

Ejercicio 1: escriba una función que permita calcular el factorial de un número. Recuerde que el factorial de 5 se define como 5! = 1*2*3*4*5 

Solución Propuesta:

CREATE OR REPLACE FUNCTION factorial(p_numero IN NUMBER) RETURN NUMBER IS v_resultado NUMBER := 1; BEGIN FOR i IN 1..p_numero LOOP v_resultado := v_resultado*i; END LOOP; RETURN v_resultado; END; . /@/home/jamslug/factorial.sql

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 59: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 59 de 121

4.3.4 Loops Anidados: Al igual que los operadores de comprobación IF ELSE y los bloques de código PL/SQL, los LOOPS pueden anidarse unos dentro de otros para formar estructuras lógicas más complejas, como puede verse en el siguiente ejemplo.

Ejemplo 1: simulador de calificaciones

1. Cree una tabla denominada simula con la siguiente estructura: ID (Number), Codigo (Number), Calificacion (Number)

2. Defina una secuencia seq_simula que comience en 1 y se incremente en 13. Escriba un bloque anónimo PL/SQL que realice lo siguiente 10 veces:

1. Inserte 1 registro en la tabla con el valor del ID obtenido de la secuencia, el Código como un número aleatorio redondeado a 0 decimales entre el 19989070 y el 79989070 y una Calificación  como un número aleatorio redondeado a 2 decimales entre el 1 y el  5.

2. Repita este procedimiento 4 veces conservando el CODIGO anterior de forma que para un mismo alumno se tengan 5 calificaciones

4. Presente un reporte de las calificaciones obtenidas

Solución Propuesta:

DROP TABLE SIMULA; CREATE TABLE SIMULA( ID NUMBER, CODIGO NUMBER, CALIFICACION NUMBER);

DROP SEQUENCE SEQ_SIMULA; CREATE SEQUENCE SEQ_SIMULA MINVALUE 1 START WITH 1 INCREMENT BY 1 CACHE 50;

--BLOQUE PL/SQL SOLICITADO DECLARE aux_codigo NUMBER := 0; BEGIN FOR i IN 1..10 LOOP --loop externo aux_codigo := ROUND(DBMS_RANDOM.VALUE(19989070,79989070),0); INSERT INTO SIMULA VALUES(SEQ_SIMULA.NEXTVAL,aux_codigo,ROUND(DBMS_RANDOM.VALUE(1,5),2)); FOR j IN 1..4 LOOP --loop interno INSERT INTO SIMULA

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 60: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 60 de 121

VALUES(SEQ_SIMULA.NEXTVAL,aux_codigo,ROUND(DBMS_RANDOM.VALUE(1,5),2)); END LOOP; --loop interno END LOOP; --loop externo END; . /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 61: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 61 de 121

5. Parámetros de Entrada y Salida – Aclaración: 

Hasta el momento los parámetros empleados en los diferentes procedimientos y funciones han sido de entrada, es decir valores que el usuario ingresa al momento de llamar al procedimiento o función y que son usados al interior del mismo para producir un resultado. Los parámetros de salida (OUT) son aquellos cuyo valor no es accesible al interior del procedimiento o función al cual se pasan, pero que pueden ser modificados desde el mismo. Los parámetros IN OUT (de entrada y salida) son aquellos cuyo valor se encuentra disponible al interior de la función o procedimiento que los llama y que pueden ser modificados desde el mismo. El siguiente ejemplo servirá para aclarar este concepto.

Ejemplo 1: Escriba un paquete denominado PARAMETERS que cuente con las siguientes funciones y procedimientos.

1. FUNCTION name_to_upper(ain_name IN VARCHAR2) RETURN VARCHAR2, esta función retorna el valor de ain_name en mayúsculas usando la función upper para tal fin

2. PROCEDURE name_to_upper2(aout_name OUT VARCHAR2), este procedimiento modifica el valor de la variable interna del paquete definiendo para la misma un nuevo valor en mayúsculas

3. PROCEDURE name_to_lower(ainout_name IN OUT VARCHAR2), este procedimiento toma el valor actual de la variable interna del paquete y lo modifica dejándolo en minúsculas, para lo cual emplea la función lower

Solución Propuesta:

Especificación del Paquete:

--ejemplo del alcance de las variables usando los --identificadores IN OUT --archivo de especificacion del paquete PARAMETERS CREATE OR REPLACE PACKAGE PARAMETERS AS

FUNCTION name_to_upper(ain_name IN VARCHAR2) RETURN VARCHAR2;

PROCEDURE name_to_upper2(aout_name OUT VARCHAR2); PROCEDURE name_to_lower(ainout_name IN OUT VARCHAR2);

--TEST UNIT PROCEDURE test;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 62: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 62 de 121

END PARAMETERS; . / 

Cuerpo del Paquete:

CREATE OR REPLACE PACKAGE BODY PARAMETERS AS

v_name VARCHAR(120):='Jack Daniels';

FUNCTION name_to_upper(ain_name IN VARCHAR2) RETURN VARCHAR2 IS

BEGIN RETURN ('valor devuelto por la funcion: '||upper(ain_name)); END name_to_upper;

PROCEDURE name_to_upper2(aout_name OUT VARCHAR2) IS

--aunque nada se muestre en pantalla la variable --interna del paquete está siendo modificada BEGIN aout_name := upper('Casper Houser'); END name_to_upper2; PROCEDURE name_to_lower(ainout_name IN OUT VARCHAR2) IS

--aunque nada se muestre en pantalla la variable --interna del paquete está siendo modificada BEGIN ainout_name := lower(ainout_name); END name_to_lower;

PROCEDURE test IS

BEGIN pl('---- UNIDAD DE PRUEBA PAQUETE: PARAMETERS ----'); pl('valor incial de la variable v_name: '||v_name); pl('llamando a la funcion name_to_upper...'); pl(name_to_upper(v_name));

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 63: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 63 de 121

pl('valor de la variable v_name luego de llamar a la funcion: '||v_name); pl('llamando al procedimiento name_to_upper2...'); name_to_upper2(v_name); pl('valor de la variable v_name luego de llamar al procedimiento: '||v_name); pl('llamando al procedimiento name_to_lower...'); name_to_lower(v_name); pl('valor de la variable v_name luego de llamar al procedimiento: '||v_name); pl('---- fin de la unidad de prueba ----'); END;

END PARAMETERS; . /

Ejercicio 1: compile el paquete parameters y ejecute su procedimiento de prueba mediante la instrucción EXECUTE PARAMETERS.test, analice la salida – recuerde definir la variable SERVEROUTPUT en ON ­

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 64: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 64 de 121

6. Manejo de Excepciones: 

Para desarrollar esta parte emplearemos una función division(n1,n2) que devolverá el valor de dividir n1 entre n2. El código de la misma es el siguiente:

--division.sql CREATE OR REPLACE FUNCTION DIVISION(N1 IN NUMBER, N2 IN NUMBER) RETURN NUMBER IS BEGIN RETURN(N1/N2); END; . /

Para probar la función podemos emplear el siguiente Test Unit: SELECT DIVISION(8,2) FROM DUAL; Sin embargo aunque la función realiza su tarea sin inconveniente ¿qué ocurre si un usuario intenta este Test Unit?: SELECT DIVISION(8,0) FROM DUAL; Aunque la función está escrita correctamente se produce una condición de terminación prematura debida al hecho de que no es posible dividir un número por 0:

ERROR at line 1: ORA-01476: divisor is equal to zero ORA-06512: at "SYSTEM.DIVISION", line 5

Debe entonces manejarse esa excepción:

--division.sql CREATE OR REPLACE FUNCTION DIVISION(N1 IN NUMBER, N2 IN NUMBER) RETURN NUMBER IS BEGIN RETURN(N1/N2); EXCEPTION WHEN ZERO_DIVIDE THEN pl('No es posible divir por 0'); END; . /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 65: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 65 de 121

Ahora cuando se ejecuta la misma unidad de prueba que generó la terminación prematura anterior se observa la siguiente salida:

ERROR at line 1: ORA-06503: PL/SQL: Function returned without value ORA-06512: at "SYSTEM.DIVISION", line 11

No es posible divir por 0

La excepción está siendo controlada, el error que aparece se debe a que una función debe retornar un valor y en este caso no lo está haciendo, por lo que podría modificarse el código para devolver un valor numérico que indique el error:

--division.sql CREATE OR REPLACE FUNCTION DIVISION(N1 IN NUMBER, N2 IN NUMBER) RETURN NUMBER IS BEGIN RETURN(N1/N2); EXCEPTION WHEN ZERO_DIVIDE THEN RETURN -1; --pl('No es posible divir por 0'); END; . /

6.1 Excepciones Comunes Incluidas en Oracle:

Existen diferentes excepciones incluidas en Oracle que permiten controlar las condiciones de error más comunes, la siguiente tabla presenta un resumen de estas:

Excepción Origen Observación

NO_DATA_FOUND Esta excepción se presenta cuando al realizar una consulta (SELECT o SELECT INTO) no se obtiene ningún valor

Esta excepción NO ocurre cuando se realiza un SELECT COUNT o SELECT SUM, porque cualquiera de estas dos funciones retorna un 0 en caso de no existir registros

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 66: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 66 de 121

TOO_MANY_ROWS Aparece cuando al realizar una consulta con SELECT INTO el resultado contiene más de una fila

ZERO_DIVIDE Aparece al intentar una división por 0

LOGIN_DENIED Ocurre cuando un usuario intenta conectarse con el servidor Oracle empleando un nombre de usuario y/o una contraseña inválidos

PROGRAM_ERROR Aparece cuando un programa PL/SQL contiene un error

VALUE_ERROR Ocurre cuando una variable no puede convertirse de un tipo a otro, o cuando el tamaño de la misma no le permite almacenar el valor solicitado

DUP_VALUE_ON_INDEX Ocurre cuando se intenta insertar un valor duplicado en una columna con restricción de valor único

Ejemplo 1: inserte en la tabla SIMULA realizada en el ejercicio anterior un nuevo valor, emplee la secuencia para obtener el ID, el valor 79799331 como CODIGO y la CALIFICACION 2. 

Escriba un procedimiento CONSULTAR_ALUMNO que reciba como parámetro de entrada (IN) el código de un alumno y traiga su calificación. El procedimiento debe manejar 2 excepciones:

1. NO_DATA_FOUND para el caso en el que se realice una consulta por un código inexistente

2. TOO_MANY_ROWS para el caso en que se realice la consulta para un usuario que tenga más de una calificación – es decir cualquiera de los códigos existentes en la tabla SIMULA a excepción del 79799331 que se insertó al comenzar este ejemplo

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 67: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 67 de 121

Solución Propuesta:

-- insercion del registro solicitado INSERT INTO SIMULA VALUES(SEQ_SIMULA.NEXTVAL,79799331,2); --Procedimiento consultar_alumno CREATE OR REPLACE PROCEDURE CONSULTAR_ALUMNO(AIN_CODIGO IN NUMBER) IS V_CAL NUMBER := 0; BEGIN SELECT S.CALIFICACION INTO V_CAL FROM SIMULA S WHERE CODIGO = AIN_CODIGO; pl('La calificacion solicitada es: '||V_CAL);

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); WHEN TOO_MANY_ROWS THEN pl('Existe mas de 1 calificacion para el Codigo: '||AIN_CODIGO); END; . /

Ejercicio 2: escriba las tres sentencias SQL con las que probaría los diferentes comportamientos del procedimiento anterior, consulte la tabla SIMULA primero para saber que parámetros usar en cada caso.

Solución Propuesta – depende de los datos de la tabla SIMULA ­:

SQL> EXECUTE CONSULTAR_ALUMNO(79799331); La calificacion solicitada es: 2

PL/SQL procedure successfully completed.

SQL> EXECUTE CONSULTAR_ALUMNO(46135886); Existe mas de 1 calificacion para el Codigo: 46135886

PL/SQL procedure successfully completed.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 68: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 68 de 121

SQL> EXECUTE CONSULTAR_ALUMNO(007); No existen calificaciones para el Codigo: 7

PL/SQL procedure successfully completed.

Es posible modificar ligeramente el código del ejemplo anterior y añadir una nueva excepción para el caso en que ocurra un error que no se ha considerado y obtener más información acerca del mismo:--Procedimiento consultar_alumno CREATE OR REPLACE PROCEDURE CONSULTAR_ALUMNO(AIN_CODIGO IN NUMBER) IS V_CAL NUMBER := 0; BEGIN SELECT S.CALIFICACION INTO V_CAL FROM SIMULA S WHERE CODIGO = AIN_CODIGO; pl('La calificacion solicitada es: '||V_CAL);

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); WHEN TOO_MANY_ROWS THEN pl('Existe mas de 1 calificacion para el Codigo: '||AIN_CODIGO); --otras excepciones que se puedan presentar WHEN OTHERS THEN raise_application_error(-20002, SQLERRM); END; . /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 69: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 69 de 121

7. Cursores: 

Hasta el momento todas las operaciones de consulta realizadas han trabajado sobre una única fila de resultados, en el caso en que existe más de una fila se ha implementado el manejo de la excepción TOO_MANY_ROWS para evitar la terminación prematura de los programas. Sin embargo PL/SQL provee las herramientas necesarias para trabajar con grupos de registros, esa herramienta recibe el nombre de CURSOR. Una definición más formal de CURSOR es la siguiente: 

Cursors are memory areas where Oracle executes SQL statements. In database programming cursors are internal data structures that allow processing of SQL query results. 

Existen dos clases de CURSORS en Oracle, los implícitos y los explícitos. Los primeros son generados de forma automática por la base de datos cada vez que un sentencia SQL se ejecuta, los segundos son definidos por el programador dentro de su código PL/SQL quien posee control sobre ellos. Para poder entender los cursores explícitos es necesario primero comprender un poco mejor el funcionamiento de los cursores implícitos:

1. Todos los bloques PL/SQL generan un CURSOR implícito cada vez que ejecutan una sentencia SQL

2. Un CURSOR se asocia de manera automática con cada operación de manipulación de datos (DML): UPDATE, SELECT, INSERT

3. Las operaciones UPDATE y DELETE cuentan con un CURSOR asociado que permite obtener información acerca del grupo de filas afectado por la operación

4. Todas las sentencias de inserción: INSERT necesitan un lugar donde almacenar temporalmente los datos que deberán ingresar en la base de datos. El CURSOR es ese lugar

5. El último CURSOR abierto recibe el nombre de SQL CURSOR

Ejercicio 1: para poder ver en acción a los cursores implícitos es necesario crear un nuevo procedimiento que trabajará sobre la tabla SIMULA creada en un ejemplo anterior. El procedimiento se llamará ACTUALIZAR_ALUMNO, recibirá como parámetro de entrada un Código y actualizará (mediante una sentencia UPDATE de SQL) las Calificaciones asociadas a dicho Código sumándole 1, siempre y cuando el resultado obtenido sea menor o igual a 5.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 70: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 70 de 121

Solución Propuesta:

--Procedimiento actualizar_alumno CREATE OR REPLACE PROCEDURE ACTUALIZAR_ALUMNO(AIN_CODIGO IN NUMBER) IS

BEGIN UPDATE SIMULA SET CALIFICACION = CALIFICACION+1 WHERE CODIGO = AIN_CODIGO AND ((CALIFICACION+1)<=5);

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); --otras excepciones que se puedan presentar WHEN OTHERS THEN raise_application_error(-20002, SQLERRM); END; . /

Puede modificarse ligeramente el código anterior para incluir un cursor que informe acerca del número de calificaciones modificadas:

--Procedimiento actualizar_alumno CREATE OR REPLACE PROCEDURE ACTUALIZAR_ALUMNO(AIN_CODIGO IN NUMBER) IS

BEGIN UPDATE SIMULA SET CALIFICACION = CALIFICACION+1 WHERE CODIGO = AIN_CODIGO AND ((CALIFICACION+1)<=5); --consulta del CURSOR implicito para saber cuantas calificaciones --fueron actualizadas pl('# de calificaciones actualizadas: '||SQL%ROWCOUNT);--SQL es el nombre del CURSOR

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 71: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 71 de 121

--otras excepciones que se puedan presentar WHEN OTHERS THEN raise_application_error(-20002, SQLERRM); END; . /

Ejercicio 2: pruebe el funcionamiento del anterior procedimiento mediante la sentencia: execute actualizar_alumno(<codigo_que_se_encuentre_en_su_tabla>); Ejecute la prueba varias veces, a medida que las calificaciones del usuario vayan incrementándose usted notará que el número de registros actualizados es menor.

7.1 Definición de un CURSOR explícito: 

Los cursores explícitos se definen en la sección de DECLARACION de un programa PL/SQL, una vez declarados se trabaja con ellos de la siguiente manera:

1. Abrir el cursor (open)2. Invocar el cursor (fetch)3. Cerrar el cursor (close)

La declaración de un CURSOR define su nombre y la sentencia SELECT de SQL asociada al mismo, la sintaxis general de declaración es la siguiente:

CURSOR c_cursor_name IS select statement

Ejemplo 1: defina un cursor que traiga todas las calificaciones asociadas a un código dado

DECLARECURSOR c_MyCursor IS SELECT CALIFICACION FROM SIMULA WHERE CODIGO = 5665789;

7.1.1 Records: 

Un RECORD es un tipo de dato compuesto, puede estar asociado a una tabla, a un CURSOR o ser definido directamente por el programador.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 72: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 72 de 121

Para declarar un Record basado en una tabla se emplea la siguiente sintaxis:

record_name table_name%ROWTYPE

Si se desea que el Record esté basado en un CURSOR se emplea la siguiente sintaxis:

record_name cursor_name%ROWTYPE

Ejemplo 1: Escriba un fragmento PL/SQL en el que se declare un RECORD asociado a la tabla SIMULA y empleo luego dentro de una sentencia SELECT INTO.

--declaracion y uso basico de un RECORD basado en TABLA DECLARE vr_infoalumno SIMULA%ROWTYPE; BEGIN SELECT * FROM SIMULA INTO vr_infoalumno WHERE CODIGO = 79799331;

Usted puede entonces usar de forma conjunta un CURSOR y un RECORD para procesar conjuntos de datos en lugar de datos independientes.

Ejemplo 2: Escriba un bloque PL/SQL que cumpla las siguientes tareas:1. Defina un CURSOR que recuperará todos los registros de la tabla SIMULA para un 

CODIGO específico2. DEFINA UN RECORD asociado a la tabla SIMULA en el que pueda irse procesando la 

información referenciada por el CURSOR anterior 3. Presente en pantalla cada uno de los registros obtenidos

Solución Propuesta:

--ejemplo basico de trabajo con CURSORES explicitos DECLARE CURSOR c_grades IS --declaracion del CURSOR SELECT * FROM SIMULA WHERE CODIGO = 46135886; vr_info SIMULA%ROWTYPE; --declaracion de un record del tipo tabla: SIMULA BEGIN OPEN c_grades; --1. Abrir el Cursor LOOP

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 73: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 73 de 121

FETCH c_grades INTO vr_info; --2. Recuperar la informacion del CURSOR EXIT WHEN(c_grades%NOTFOUND); --Condicion para salir del LOOP: no hay mas registros pl(vr_info.ID||' '||vr_info.CODIGO||' '||vr_info.CALIFICACION); END LOOP; CLOSE c_grades; --3. Cerrar el Cursor END; . /

Ejercicio 1: compile el anterior bloque PL/SQL empleando un CODIGO que se encuentre en su tabla SIMULA, ejecútelo y analice los resultados obtenidos.

Ejemplo 3: modifique el ejemplo anterior para que la consulta sólo recupere los campos ID y CALIFICACION.

--ejemplo basico de trabajo con CURSORES explicitos DECLARE CURSOR c_grades IS --declaracion del CURSOR SELECT ID,CALIFICACION FROM SIMULA WHERE CODIGO = 46135886; vr_info c_grades%ROWTYPE24; --declaracion de un record del tipo CURSOR BEGIN OPEN c_grades; --1. Abrir el Cursor LOOP FETCH c_grades INTO vr_info; --2. Recuperar la informacion del CURSOR EXIT WHEN(c_grades%NOTFOUND); --Condicion para salir del LOOP: no hay mas registros pl(vr_info.ID||' '||vr_info.CALIFICACION); END LOOP; CLOSE c_grades; --3. Cerrar el Cursor END; . /

Ejercicio 2: compile el anterior bloque PL/SQL empleando un CODIGO que se encuentre en su tabla SIMULA, ejecútelo y analice los resultados obtenidos. 

24 Al declarar un RECORD del mismo tipo del CURSOR se cuenta con un contenedor idóneo para la información que será recuperada de este último.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 74: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 74 de 121

Ejercicio 3: ahora que conoce el concepto de CURSORS y RECORDS puede modificar el código del procedimiento almacenado CONSULTAR_ALUMNO, para que en lugar de capturar una excepción cuando hay más de una calificación por alumno, presente las calificaciones de los alumnos siempre y cuando el código ingresado se encuentre en la base de datos.

Solución Propuesta:

--Procedimiento consultar_alumno CREATE OR REPLACE PROCEDURE CONSULTAR_ALUMNO2(AIN_CODIGO IN NUMBER) IS CURSOR c_infoalumno IS SELECT * FROM SIMULA WHERE CODIGO = AIN_CODIGO; vr_alumnocal c_infoalumno%ROWTYPE; --debe funcionar igual si se usa SIMULA%ROWTYPE BEGIN OPEN c_infoalumno; --1.Open Cursor pl('--- CALIFICACIONES DISPONIBLES PARA EL CODIGO: '||AIN_CODIGO); LOOP FETCH c_infoalumno INTO vr_alumnocal; --2.Fetch Cursor EXIT WHEN(c_infoalumno%NOTFOUND); pl('ID: '||vr_alumnocal.ID||' CALIFICACION: '||vr_alumnocal.CALIFICACION); END LOOP;

CLOSE c_infoalumno; --3.Close Cursor

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); --otras excepciones que se puedan presentar WHEN OTHERS THEN raise_application_error(-20002, SQLERRM); END; . /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 75: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 75 de 121

Ejercicio 4: qué ocurre con el procedimiento anterior cuando se ingresa un código que no existe en la tabla?

7.2 Atributos de los CURSORES: 

Los siguientes son los atributos de los cursores que pueden usarse en un programa PL/SQL

Nombre Forma de Uso Descripción

%NOTFOUND nombrecursor%NOTFOUND Este atributo devuelve TRUE si la operación FETCH inmediatamente anterior NO recuperó un registro válido o FALSE cuando es posible hacerlo

%FOUND nombrecursor%FOUND Este atributo devuelve TRUE si la operación FETCH inmediatamente anterior recuperó un registro válido o FALSE cuando NO es posible hacerlo

%ROWCOUNT nombrecursor%ROWCOUNT Permite conocer el número de registros que han sido recuperados con las operaciones FETCH anteriores

%ISOPEN nombrecursor%ISOPEN Devuelve TRUE si el cursor consultado se encuentra abierto, FALSE en caso contrario

Ejercicio 5: haciendo uso de los atributos comunes de los CURSORES y trabajando sobre la tabla SIMULA complemente el procedimiento CONSULTAR_ALUMNO2 para que realice las siguientes tareas:

1. Evalúe el número de calificaciones disponibles para el CODIGO solicitado y lo imprima en pantalla en la misma línea en la que está imprimiendo el código

2. Emplee un WHILE LOOP haciendo uso del atributo %FOUND en lugar del EXIT WHEN que está utilizando hasta el momento

3. Verifique si el cursor se encuentra abierto antes de cerrarlo – lo cual por supuesto será así – imprima un mensaje advirtiendo este hecho

4. Cierre el cursor y vuelva a verificar si se encuentra abierto, imprima un mensaje 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 76: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 76 de 121

advirtiendo el nuevo estado del cursor

Solución Propuesta:

--Procedimiento consultar_alumno CREATE OR REPLACE PROCEDURE CONSULTAR_ALUMNO3(AIN_CODIGO IN NUMBER) IS CURSOR c_infoalumno IS SELECT * FROM SIMULA WHERE CODIGO = AIN_CODIGO; vr_alumnocal c_infoalumno%ROWTYPE; v_lastid NUMBER := 0; BEGIN pl('--- CALIFICACIONES DISPONIBLES PARA EL CODIGO: '||AIN_CODIGO); OPEN c_infoalumno; --1.Open Cursor FETCH c_infoalumno INTO vr_alumnocal; --2.Fetch Cursor - la primera vez fuera del loop pl('ID: '||vr_alumnocal.ID||' CALIFICACION: '||vr_alumnocal.CALIFICACION); WHILE (c_infoalumno%FOUND) LOOP v_lastid := vr_alumnocal.ID; FETCH c_infoalumno INTO vr_alumnocal; --2.Fetch Cursor IF(v_lastid!=vr_alumnocal.ID) THEN -- evita que la ultima linea aparezca dos veces pl('ID: '||vr_alumnocal.ID||' CALIFICACION: '||vr_alumnocal.CALIFICACION); END IF; END LOOP; pl('Total Calificaciones Obtenidas: '||c_infoalumno%ROWCOUNT); IF(c_infoalumno%ISOPEN) THEN pl('Estado del Cursor: Open'); ELSE pl('Estado del Cursor: Closed'); END IF;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 77: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 77 de 121

CLOSE c_infoalumno; --3.Close Cursor IF(c_infoalumno%ISOPEN) THEN pl('Estado del Cursor: Open'); ELSE pl('Estado del Cursor: Closed'); END IF;

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); --otras excepciones que se puedan presentar WHEN OTHERS THEN raise_application_error(-20002, SQLERRM); END; . /

Es posible modificar el código anterior para incluir una excepción que se lance cuando no se cierra el CURSOR:

--Procedimiento consultar_alumno CREATE OR REPLACE PROCEDURE CONSULTAR_ALUMNO3(AIN_CODIGO IN NUMBER) IS CURSOR c_infoalumno IS SELECT * FROM SIMULA WHERE CODIGO = AIN_CODIGO; vr_alumnocal c_infoalumno%ROWTYPE; --debe funcionar igual si se usa SIMULA%ROWTYPE v_lastid NUMBER := 0; BEGIN pl('--- CALIFICACIONES DISPONIBLES PARA EL CODIGO: '||AIN_CODIGO); OPEN c_infoalumno; --1.Open Cursor FETCH c_infoalumno INTO vr_alumnocal; --2.Fetch Cursor - la primera vez fuera del loop pl('ID: '||vr_alumnocal.ID||' CALIFICACION: '||vr_alumnocal.CALIFICACION);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 78: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 78 de 121

WHILE (c_infoalumno%FOUND) LOOP v_lastid := vr_alumnocal.ID; FETCH c_infoalumno INTO vr_alumnocal; --2.Fetch Cursor IF(v_lastid!=vr_alumnocal.ID) THEN -- evita que la ultima linea aparezca dos veces pl('ID: '||vr_alumnocal.ID||' CALIFICACION: '||vr_alumnocal.CALIFICACION); END IF; END LOOP; pl('Total Calificaciones Obtenidas: '||c_infoalumno%ROWCOUNT); IF(c_infoalumno%ISOPEN) THEN pl('Estado del Cursor: Open'); ELSE pl('Estado del Cursor: Closed'); END IF;

CLOSE c_infoalumno; --3.Close Cursor IF(c_infoalumno%ISOPEN) THEN pl('Estado del Cursor: Open'); ELSE pl('Estado del Cursor: Closed'); END IF;

--manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); --otras excepciones que se puedan presentar WHEN OTHERS THEN IF(c_infoalumno%ISOPEN) THEN CLOSE c_infoalumno; --3.Close Cursor END IF;

END; . /

Es muy importante cerrar siempre los cursores explícitos – los implícitos son cerrados por ORACLE automáticamente – pues de esta manera se liberan los recursos de memoria 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 79: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 79 de 121

asignados a cada uno de ellos, en caso contrario un programa en PL/SQL podría consumir demasiados recursos de Hardware, hasta llegar al punto de tumbar el servidor.

7.3 Cursor For Loop: 

Es posible emplear una sintaxis alternativa con los CURSORS que permite manejar de manera implícita las operaciones de OPEN, FETCH Y CLOSE. A esta sintaxis se le conoce como un CURSOR FOR LOOP

Ejemplo 1: reescriba el procedimiento consultar alumno empleando un Cursor For Loop

Solución Propuesta:

--Procedimiento consultar_alumno empleando un Cursor For Loop CREATE OR REPLACE PROCEDURE CONSULTAR_ALUMNO4(AIN_CODIGO IN NUMBER) IS CURSOR c_infoalumno IS SELECT * FROM SIMULA WHERE CODIGO = AIN_CODIGO; BEGIN pl('--- CALIFICACIONES DISPONIBLES PARA EL CODIGO: '||AIN_CODIGO); FOR vr_alumnocal25 IN c_infoalumno LOOP 26

pl('ID: '||vr_alumnocal.ID||' CALIFICACION: '||vr_alumnocal.CALIFICACION); END LOOP; --manejo de excepciones EXCEPTION WHEN NO_DATA_FOUND THEN pl('No existen calificaciones para el Codigo: '||AIN_CODIGO); --otras excepciones que se puedan presentar WHEN OTHERS THEN IF(c_infoalumno%ISOPEN) THEN CLOSE c_infoalumno; --3.Close Cursor END IF; END; . /

25 Este RECORD se está definiendo implícitamente del mismo tipo que el CURSOR26 Las operaciones OPEN, FETCH y CLOSE están siendo manejadas implícitamente por el motor ORACLE

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 80: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 80 de 121

7.4 Parámetros para los Cursores: 

Al igual que las funciones y procedimientos, es posible definir un CURSOR con parámetros, esto permite una mayor flexibilidad con los CURSORES y los hace más reutilizables.

Ejemplo 1: Escriba un bloque de PL/SQL que sume los factoriales de 2 números ingresados por el usuario. Emplee la función factorial que creo en un ejercicio anterior.

--Bloque PL/SQL que suma los factoriales de dos numeros DECLARE v_n1 NUMBER := &n1; v_n2 NUMBER := &n2; v_aux1 NUMBER := 0; v_aux2 NUMBER := 0; CURSOR c_factorial (vc_numero IN NUMBER) IS SELECT factorial(vc_numero) FROM DUAL; BEGIN OPEN c_factorial(v_n1);--OPEN cursor + PARAM FETCH c_factorial INTO v_aux1; --FECTH cursor CLOSE c_factorial; --CLOSE CURSOR OPEN c_factorial(v_n2);--OPEN cursor + PARAM FETCH c_factorial INTO v_aux2; --FECTH cursor CLOSE c_factorial; --CLOSE CURSOR v_aux2 := v_aux2 + v_aux1; pl(v_n1||'!+'||v_n2||'!='||v_aux2); END; . /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 81: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 81 de 121

7.5 Cursores anidados: 

Los cursores pueden anidarse al igual que ocurre con los LOOPS que se vieron en lecciones anteriores. El siguiente ejemplo ilustra esta idea.

Ejemplo 1: Considere el siguiente modelo:

1. Escriba el código SQL que se encargue de generar este modelo27 en la base de datos, tenga en cuenta las llaves primarias y las llaves foráneas. Tenga en cuenta también que el documento de cada estudiante será único.

2. Cree una secuencia para las tablas que lo necesitan, el nombre de estas secuencias debe ser SEQ_<nombre_tabla>. Estas secuencias comenzarán en 1 y se incrementarán en valores de 1. Escriba el código SQL.

3. Escriba el código SQL que inserte los siguientes 5 estudiantes en la tabla correspondiente:1. Juan Perez 798990122. Rosa Hernandez 527800413. María Castro 539007894. Carlos Hernandez 800010005. Pedro Marin 79998901

4. Escriba el código SQL que inserte 5 cursos en la tabla correspondiente1. Matematicas2. Geometria3. Biologia4. Bases de Datos5. Programacion Orientada a Objetos

5. Escriba un bloque – o varios ­ PL/SQL que inscriban a los estudiantes en las materias 

27 Todos los campos ID serán de tipo NUMBER, el campo DOCUMENTO_ESTUDIANTE será de tipo VARCHAR2(20), el campo FECHA_EVALUACION de tipo DATE, el campo CALIFICACION de tipo NUMBER, los demás campos tipo VARCHAR2(50)

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 82: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 82 de 121

de la siguiente manera:1. En cada materia deben quedar inscritos como mínimo dos estudiantes como 

máximo 4 (sugerencia emplee un Cursor For Loop)2. Ningún estudiante puede quedar inscrito en más de 3 materias (sugerencia emplee 

LOOPS anidados)3. Ningún estudiante pueda quedar inscrito en menos de 1 materia (sugerencia 

emplee LOOPS anidados)28

6. Se habrán realizado tres evaluaciones para cada materia, las evaluaciones fueron realizadas los días: 17 de Enero, 17 de Febrero y 17 de Marzo del año en curso. Escriba el código SQL (Sugerencia un bloque iterativo podría ayudarle a optimizar este proceso)

7. Escriba un bloque de PL/SQL que otorguen a los estudiantes inscritos en cada materia las calificaciones obtenidas en las tres evaluaciones realizadas – sugerencia emplee For Cursor Loops anidados ­, cumpliendo con las siguientes reglas:1. La nota mínima es 12. La nota máxima es 53. Se emplean calificaciones con 2 decimales

8. Escriba un paquete en PL/SQL que cuente con las siguientes funciones y o procedimientos:1. INFO_CURSO(VID_CURSO IN NUMBER): recibe como parámetro el ID de un 

curso. Imprime en pantalla la siguiente información:1. Nombre del Curso2. Alumnos Inscritos3. Nota Media4. Nota Mínima5. Nota Máxima

2. INFO_ESTUDIANTE(VDOCUMENTO_ESTUDIANTE IN VARCHAR2): recibe como parámetro el DOCUMENTO de un alumno. Imprime en pantalla la siguiente información:1. Nombre y Apellido del Estudiante 2. Para cada una de las materias inscritas:

1. Nombre de la Materia2. Para cada uno de los exámenes presentados en  ­ esa ­ materia:

1. Nota obtenida en el examen3. Nota Promedio obtenida en la materia (definida como la suma de las 

calificaciones obtenidas en los exámenes dividido entre el número de exámenes)

3. Nota Máxima obtenida en todas las materias (definida como la calificación más alta obtenida en todas las evaluaciones – DE TODOS LOS CURSOS ­ presentadas por este alumno)

28 Se sugiere realizar estos dos puntos en scripts separados al menos mientras se adquiere practica con los CURSORS y los LOOPS anidados

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 83: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 83 de 121

4. Nota Mínima obtenida en todas las materias (definida como la calificación más baja obtenida en todas las evaluaciones – DE TODOS LOS CURSOS ­ presentadas por este alumno)

Solución Propuesta:

Punto 1 y 2:

--archivo para la creacion de tablas del modelo --requerido para la parte final del curso

DROP TABLE ESTUDIANTE_EVALUACION; DROP TABLE CURSO_ESTUDIANTE; DROP TABLE EVALUACION; DROP TABLE CURSO; DROP TABLE ESTUDIANTE;

CREATE TABLE ESTUDIANTE( ID_ESTUDIANTE NUMBER, DOCUMENTO_ESTUDIANTE VARCHAR2(20) NOT NULL, NOMBRE_ESTUDIANTE VARCHAR2(50) NOT NULL, APELLIDO_ESTUDIANTE VARCHAR2(50) NOT NULL);

ALTER TABLE ESTUDIANTE ADD CONSTRAINT EST_PKC PRIMARY KEY(ID_ESTUDIANTE);

ALTER TABLE ESTUDIANTE ADD CONSTRAINT EST_UKC UNIQUE(DOCUMENTO_ESTUDIANTE);

CREATE TABLE CURSO( ID_CURSO NUMBER, NOMBRE_CURSO VARCHAR2(50) NOT NULL);

ALTER TABLE CURSO ADD CONSTRAINT CURSO_PKC PRIMARY KEY(ID_CURSO);

ALTER TABLE CURSO ADD CONSTRAINT CURSO_UKC UNIQUE(NOMBRE_CURSO);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 84: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 84 de 121

CREATE TABLE EVALUACION( ID_EVALUACION NUMBER, ID_CURSO NUMBER, FECHA_EVALUACION DATE NOT NULL);

ALTER TABLE EVALUACION ADD CONSTRAINT EVAL_PKC PRIMARY KEY(ID_EVALUACION); ALTER TABLE EVALUACION ADD CONSTRAINT EVAL_FKC FOREIGN KEY(ID_CURSO) REFERENCES CURSO(ID_CURSO);

CREATE TABLE CURSO_ESTUDIANTE( ID_CURSO NUMBER, ID_ESTUDIANTE NUMBER);

ALTER TABLE CURSO_ESTUDIANTE ADD CONSTRAINT CEST_PKC PRIMARY KEY(ID_CURSO, ID_ESTUDIANTE)29;

ALTER TABLE CURSO_ESTUDIANTE ADD CONSTRAINT CEST_FKC1 FOREIGN KEY30(ID_CURSO) REFERENCES CURSO(ID_CURSO);

ALTER TABLE CURSO_ESTUDIANTE ADD CONSTRAINT CEST_FKC2 FOREIGN KEY31(ID_ESTUDIANTE) REFERENCES ESTUDIANTE(ID_ESTUDIANTE);

CREATE TABLE ESTUDIANTE_EVALUACION( ID_ESTUDIANTE NUMBER, ID_EVALUACION NUMBER, CALIFICACION NUMBER NOT NULL);

ALTER TABLE ESTUDIANTE_EVALUACION ADD CONSTRAINT EEVAL_PKC PRIMARY KEY(ID_ESTUDIANTE, ID_EVALUACION);

ALTER TABLE ESTUDIANTE_EVALUACION

29 Llave primaria compuesta30 Primera llave foránea de la tabla31 Segunda llave foránea de la tabla

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 85: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 85 de 121

ADD CONSTRAINT EEVAL_FKC1 FOREIGN KEY(ID_ESTUDIANTE) REFERENCES ESTUDIANTE(ID_ESTUDIANTE);

ALTER TABLE ESTUDIANTE_EVALUACION ADD CONSTRAINT EEVAL_FKC2 FOREIGN KEY(ID_EVALUACION) REFERENCES EVALUACION(ID_EVALUACION);

--secuencias DROP SEQUENCE SEQ_ESTUDIANTE; DROP SEQUENCE SEQ_CURSO; DROP SEQUENCE SEQ_EVALUACION;

CREATE SEQUENCE SEQ_ESTUDIANTE MINVALUE 1 START WITH 1 INCREMENT BY 1;

CREATE SEQUENCE SEQ_CURSO MINVALUE 1 START WITH 1 INCREMENT BY 1;

CREATE SEQUENCE SEQ_EVALUACION MINVALUE 1 START WITH 1 INCREMENT BY 1; .

Punto 3 y 4:

-- insercion de los estudiantes INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'79899012','Juan','Perez'); INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'52780041','Rosa','Hernandez'); INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'53900789','María','Castro'); INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'80001000','Carlos','Hernandez'); INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'79998901','Pedro','Marin');

--insercion de las materias

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 86: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 86 de 121

INSERT INTO CURSO VALUES(SEQ_CURSO.NEXTVAL,'Matematicas'); INSERT INTO CURSO VALUES(SEQ_CURSO.NEXTVAL,'Geometria'); INSERT INTO CURSO VALUES(SEQ_CURSO.NEXTVAL,'Biologia'); INSERT INTO CURSO VALUES(SEQ_CURSO.NEXTVAL,'Bases de Datos'); INSERT INTO CURSO VALUES(SEQ_CURSO.NEXTVAL,'Programacion Orientada a Objetos');

--importante para no perder el trabajo en la proxima sesion COMMIT; .

Punto 5:

--temporal para probar muchas veces /* DROP TABLE CURSO_ESTUDIANTE; CREATE TABLE CURSO_ESTUDIANTE( ID_CURSO NUMBER, ID_ESTUDIANTE NUMBER);

ALTER TABLE CURSO_ESTUDIANTE ADD CONSTRAINT CEST_PKC PRIMARY KEY(ID_CURSO, ID_ESTUDIANTE);

ALTER TABLE CURSO_ESTUDIANTE ADD CONSTRAINT CEST_FKC1 FOREIGN KEY(ID_CURSO) REFERENCES CURSO(ID_CURSO);

ALTER TABLE CURSO_ESTUDIANTE ADD CONSTRAINT CEST_FKC2 FOREIGN KEY(ID_ESTUDIANTE) REFERENCES ESTUDIANTE(ID_ESTUDIANTE); */ --temporal para probar muchas veces

DECLARE

V_MAT NUMBER := 0; V_EST NUMBER := 0;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 87: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 87 de 121

V_AUX NUMBER := 0; V_AUX2 NUMBER := 0; CURSOR C_MATERIA IS SELECT ID_CURSO FROM CURSO; BEGIN

FOR R_MATERIA IN C_MATERIA LOOP --En cada materia deben quedar inscritos como minimo 2 estudiantes como maximo 4 FOR i IN 1..ROUND(DBMS_RANDOM.VALUE(2,4),0) LOOP LOOP V_AUX := ROUND(DBMS_RANDOM.VALUE(1,5),0); --un estudiante al azar SELECT COUNT(ID_ESTUDIANTE) INTO V_MAT FROM CURSO_ESTUDIANTE WHERE ID_ESTUDIANTE = V_AUX; --Ningun estudiante puede quedar inscrito en más de 3 materias IF(V_MAT <= 3)THEN --pero tampoco puede estar inscrito dos veces en la misma materia SELECT COUNT(ID_ESTUDIANTE) INTO V_AUX2 FROM CURSO_ESTUDIANTE WHERE ID_CURSO = R_MATERIA.ID_CURSO AND ID_ESTUDIANTE = V_AUX; IF(V_AUX2 = 0)THEN INSERT INTO CURSO_ESTUDIANTE VALUES(R_MATERIA.ID_CURSO,V_AUX); EXIT;--Si se inserta el registro, se sale de este loop END IF; END IF; END LOOP; END LOOP; END LOOP; --importante para no perder el trabajo en la proxima sesion COMMIT; EXCEPTION WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);

END;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 88: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 88 de 121

. /

Punto 6:

BEGIN FOR i IN 1..5 LOOP --loop para las materias INSERT INTO EVALUACION VALUES(SEQ_EVALUACION.NEXTVAL,i,to_date('17-01-09','DD-MM-YY')); INSERT INTO EVALUACION VALUES(SEQ_EVALUACION.NEXTVAL,i,to_date('17-02-09','DD-MM-YY')); INSERT INTO EVALUACION VALUES(SEQ_EVALUACION.NEXTVAL,i,to_date('17-03-09','DD-MM-YY')); END LOOP; --importante para no perder el trabajo en la proxima sesion COMMIT; EXCEPTION WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);

END; . /

Punto 7:

--Bloque PL/SQL que asigna las calificaciones para las evaluaciones existentes DECLARE CURSOR C_MATERIA IS --TODOS LOS CURSOS SELECT * FROM CURSO; CURSOR C_ESTUDIANTE(VIN_IDCURSO IN NUMBER) IS --TODOS LOS ESTUDIANTES EN UN CURSO SELECT * FROM CURSO_ESTUDIANTE WHERE ID_CURSO = VIN_IDCURSO; CURSOR C_EVALUACION(VIN_IDCURSO IN NUMBER) IS --TODAS LAS EVALUACIONES PARA UN CURSO

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 89: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 89 de 121

SELECT * FROM EVALUACION WHERE ID_CURSO = VIN_IDCURSO; BEGIN FOR R_MATERIA IN C_MATERIA LOOP FOR R_ESTUDIANTE IN C_ESTUDIANTE(R_MATERIA.ID_CURSO) LOOP FOR R_EVALUACION IN C_EVALUACION(R_MATERIA.ID_CURSO) LOOP INSERT INTO ESTUDIANTE_EVALUACION VALUES(R_ESTUDIANTE.ID_ESTUDIANTE,R_EVALUACION.ID_EVALUACION,ROUND(DBMS_RANDOM.VALUE(1,5),2)); END LOOP; END LOOP; END LOOP; --importante para no perder el trabajo en la proxima sesion COMMIT; EXCEPTION WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM); END; . /

Punto 8:Spec. del Paquete:

CREATE OR REPLACE PACKAGE COURSES AS FUNCTION NOTA_MEDIA_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER; FUNCTION NOTA_MINIMA_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER; FUNCTION NOTA_MAXIMA_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER; FUNCTION NOTA_MEDIA_ESTUDIANTE(VID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE, VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER; FUNCTION NOTA_MINIMA_ESTUDIANTE(VID_ESTUDIANTE IN

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 90: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 90 de 121

ESTUDIANTE.ID_ESTUDIANTE%TYPE) RETURN NUMBER; FUNCTION NOTA_MAXIMA_ESTUDIANTE(VID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE) RETURN NUMBER; PROCEDURE INFO_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE); PROCEDURE INFO_ESTUDIANTE(VDOCUMENTO_ESTUDIANTE IN ESTUDIANTE.DOCUMENTO_ESTUDIANTE%TYPE); /* PROCEDURE HELP32; PROCEDURE TEST33; */ END COURSES; . /

Body del Paquete:

CREATE OR REPLACE PACKAGE BODY COURSES AS --DADO UN ID_CURSO RECUPERAR TODAS LAS EVALUACIONES DE ESE CURSO CURSOR C_EVALUACIONES(CVID_CURSO IN CURSO.ID_CURSO%TYPE) IS SELECT ID_EVALUACION FROM EVALUACION WHERE ID_CURSO = CVID_CURSO; --DADO UN ID_ESTUDIANTE RECUPERAR EL VALOR MAXIMO --DENTRO DE TODAS LAS EVALUACIONES DE ESE ESTUDIANTE CURSOR C_EVALUACIONESEST1(CVID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE) IS SELECT MAX(CALIFICACION) AS NOTAMAXIMA FROM ESTUDIANTE_EVALUACION WHERE ID_ESTUDIANTE = CVID_ESTUDIANTE; R_EVALUACIONESEST1 C_EVALUACIONESEST1%ROWTYPE;

32 Aunque no están definidos en este ejemplo es una práctica recomendable incluir en sus paquetes un procedimiento que sirva como ayuda para quien desea usarlo.

33 Resulta muy recomendable también incluir un procedimiento que cuente con las diferentes unidades de prueba para cada función y procedimiento definido en el paquete.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 91: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 91 de 121

--DADO UN ID_ESTUDIANTE RECUPERAR EL VALOR MAXIMO --DENTRO DE TODAS LAS EVALUACIONES DE ESE ESTUDIANTE CURSOR C_EVALUACIONESEST2(CVID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE) IS SELECT MIN(CALIFICACION) AS NOTAMINIMA FROM ESTUDIANTE_EVALUACION WHERE ID_ESTUDIANTE = CVID_ESTUDIANTE; R_EVALUACIONESEST2 C_EVALUACIONESEST2%ROWTYPE; --DADO UN ID_EVALUACION RECUPERAR LA SUMA DE --LAS CALIFICACIONES PARA ESA EVALUACION CURSOR CC_CALIFICACIONES1(CVID_EVALUACION IN ESTUDIANTE_EVALUACION.ID_EVALUACION%TYPE) IS SELECT SUM(CALIFICACION) AS TOTALSUMA FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = CVID_EVALUACION; R_CALIFICACIONES1 CC_CALIFICACIONES1%ROWTYPE; --DADO UN ID_EVALUACION RECUPERAR EL NUMERO DE --CALIFICACIONES PARA ESA EVALUACION CURSOR CC_CALIFICACIONES2(CVID_EVALUACION IN ESTUDIANTE_EVALUACION.ID_EVALUACION%TYPE) IS SELECT COUNT(CALIFICACION) AS NUMEVAL FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = CVID_EVALUACION; R_CALIFICACIONES2 CC_CALIFICACIONES2%ROWTYPE; --DADO UN ID_EVALUACION RECUPERAR EL VALOR MINIMO DE --LAS CALIFICACIONES PARA ESA EVALUACION CURSOR CC_CALIFICACIONES3(CVID_EVALUACION IN ESTUDIANTE_EVALUACION.ID_EVALUACION%TYPE) IS SELECT MIN(CALIFICACION) AS NOTAMINIMA FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = CVID_EVALUACION; R_CALIFICACIONES3 CC_CALIFICACIONES3%ROWTYPE; --DADO UN ID_EVALUACION RECUPERAR EL VALOR MAXIMO DE --LAS CALIFICACIONES PARA ESA EVALUACION CURSOR CC_CALIFICACIONES4(CVID_EVALUACION IN ESTUDIANTE_EVALUACION.ID_EVALUACION%TYPE) IS

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 92: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 92 de 121

SELECT MAX(CALIFICACION) AS NOTAMAXIMA FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = CVID_EVALUACION; R_CALIFICACIONES4 CC_CALIFICACIONES4%ROWTYPE; --DADO EL ID_CURSO RECUPERAR LOS ESTUDIANTES INSCRITOS CURSOR C_ESTUDIANTES_INSCRITOS(CVID_CURSO IN CURSO.ID_CURSO%TYPE) IS SELECT * FROM ESTUDIANTE E JOIN CURSO_ESTUDIANTE CE ON E.ID_ESTUDIANTE = CE.ID_ESTUDIANTE WHERE CE.ID_CURSO = CVID_CURSO; --DADO EL ID_ESTUDIANTE RECUPERAR LOS CURSOS EN LOS --QUE SE ENCUENTRA INSCRITO CURSOR C_INSCRITOEN(CVID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE) IS SELECT ID_CURSO FROM CURSO_ESTUDIANTE WHERE ID_ESTUDIANTE = CVID_ESTUDIANTE; FUNCTION NOTA_MEDIA_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER IS V_AUX_COUNTER NUMBER := 0; V_AUX_SUM NUMBER := 0; BEGIN FOR R_EVALUACIONES IN C_EVALUACIONES(VID_CURSO) LOOP V_AUX_COUNTER := V_AUX_COUNTER + 1; OPEN CC_CALIFICACIONES1(R_EVALUACIONES.ID_EVALUACION); FETCH CC_CALIFICACIONES1 INTO R_CALIFICACIONES1; CLOSE CC_CALIFICACIONES1; OPEN CC_CALIFICACIONES2(R_EVALUACIONES.ID_EVALUACION); FETCH CC_CALIFICACIONES2 INTO R_CALIFICACIONES2; CLOSE CC_CALIFICACIONES2; V_AUX_SUM := V_AUX_SUM+(R_CALIFICACIONES1.TOTALSUMA/R_CALIFICACIONES2.NUMEVAL); END LOOP; RETURN ROUND((V_AUX_SUM/V_AUX_COUNTER),2);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 93: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 93 de 121

END NOTA_MEDIA_CURSO; FUNCTION NOTA_MINIMA_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER IS V_NOTA_ACTUAL NUMBER := 0; V_NOTA_MINIMA NUMBER := 5; --SE BUSCA QUE LA PRIMERA NOTA OBTENIDA SEA MENOR BEGIN FOR R_EVALUACIONES IN C_EVALUACIONES(VID_CURSO) LOOP OPEN CC_CALIFICACIONES3(R_EVALUACIONES.ID_EVALUACION); FETCH CC_CALIFICACIONES3 INTO R_CALIFICACIONES3; CLOSE CC_CALIFICACIONES3; V_NOTA_ACTUAL := R_CALIFICACIONES3.NOTAMINIMA; IF(V_NOTA_ACTUAL < V_NOTA_MINIMA) THEN V_NOTA_MINIMA := V_NOTA_ACTUAL;--EN CASO DE QUE LA NOTA ACTUAL SEA MAYOR QUE LA MINIMA OBTENIDA NO HAY CAMBIO END IF; END LOOP; RETURN V_NOTA_MINIMA; END NOTA_MINIMA_CURSO; FUNCTION NOTA_MAXIMA_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER IS V_NOTA_ACTUAL NUMBER := 0; V_NOTA_MAXIMA NUMBER := 0; --SE BUSCA QUE LA PRIMERA NOTA OBTENIDA SEA MAYOR BEGIN FOR R_EVALUACIONES IN C_EVALUACIONES(VID_CURSO) LOOP OPEN CC_CALIFICACIONES4(R_EVALUACIONES.ID_EVALUACION); FETCH CC_CALIFICACIONES4 INTO R_CALIFICACIONES4; CLOSE CC_CALIFICACIONES4; V_NOTA_ACTUAL := R_CALIFICACIONES4.NOTAMAXIMA; IF(V_NOTA_ACTUAL > V_NOTA_MAXIMA) THEN V_NOTA_MAXIMA := V_NOTA_ACTUAL;--EN CASO DE QUE LA NOTA ACTUAL SEA MAYOR QUE LA MINIMA OBTENIDA NO HAY CAMBIO END IF;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 94: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 94 de 121

END LOOP; RETURN V_NOTA_MAXIMA; END NOTA_MAXIMA_CURSO; FUNCTION NOTA_MEDIA_ESTUDIANTE(VID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE, VID_CURSO IN CURSO.ID_CURSO%TYPE) RETURN NUMBER IS V_AUX_SUMA NUMBER := 0; V_AUX NUMBER := 0; V_AUX_CONT NUMBER := 0; BEGIN --RECUPERAR EVALUACIONES PARA ESTE CURSO FOR R_EVALUACIONES IN C_EVALUACIONES(VID_CURSO) LOOP V_AUX_CONT := V_AUX_CONT+1; SELECT CALIFICACION INTO V_AUX FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = R_EVALUACIONES.ID_EVALUACION AND ID_ESTUDIANTE = VID_ESTUDIANTE; V_AUX_SUMA := V_AUX_SUMA + V_AUX; END LOOP; RETURN ROUND((V_AUX_SUMA/V_AUX_CONT),2); END NOTA_MEDIA_ESTUDIANTE; FUNCTION NOTA_MINIMA_ESTUDIANTE(VID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE) RETURN NUMBER IS BEGIN OPEN C_EVALUACIONESEST2(VID_ESTUDIANTE); FETCH C_EVALUACIONESEST2 INTO R_EVALUACIONESEST2; CLOSE C_EVALUACIONESEST2; RETURN R_EVALUACIONESEST2.NOTAMINIMA; END NOTA_MINIMA_ESTUDIANTE;

FUNCTION NOTA_MAXIMA_ESTUDIANTE(VID_ESTUDIANTE IN ESTUDIANTE.ID_ESTUDIANTE%TYPE) RETURN NUMBER IS BEGIN OPEN C_EVALUACIONESEST1(VID_ESTUDIANTE);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 95: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 95 de 121

FETCH C_EVALUACIONESEST1 INTO R_EVALUACIONESEST1; CLOSE C_EVALUACIONESEST1; RETURN R_EVALUACIONESEST1.NOTAMAXIMA; END NOTA_MAXIMA_ESTUDIANTE; PROCEDURE INFO_CURSO(VID_CURSO IN CURSO.ID_CURSO%TYPE) IS V_AUXNOMBRE CURSO.NOMBRE_CURSO%TYPE; BEGIN SELECT NOMBRE_CURSO INTO V_AUXNOMBRE FROM CURSO WHERE ID_CURSO = VID_CURSO; PL('--- INFORMACION SOLICITADA DEL CURSO ---'); PL('Nombre del Curso: '||V_AUXNOMBRE); PL('Alumnos Inscritos: '); FOR R_ESTUDIANTES_INSCRITOS IN C_ESTUDIANTES_INSCRITOS(VID_CURSO) LOOP PL(R_ESTUDIANTES_INSCRITOS.NOMBRE_ESTUDIANTE||' '||R_ESTUDIANTES_INSCRITOS.APELLIDO_ESTUDIANTE||' '||R_ESTUDIANTES_INSCRITOS.DOCUMENTO_ESTUDIANTE); END LOOP; PL('Nota Media Obtenida por los Estudiantes: '||to_char(NOTA_MEDIA_CURSO(VID_CURSO))); PL('Nota Minima Obtenida por los Estudiantes: '||to_char(NOTA_MINIMA_CURSO(VID_CURSO))); PL('Nota Maxima Obtenida por los Estudiantes: '||to_char(NOTA_MAXIMA_CURSO(VID_CURSO))); END INFO_CURSO;

PROCEDURE INFO_ESTUDIANTE(VDOCUMENTO_ESTUDIANTE IN ESTUDIANTE.DOCUMENTO_ESTUDIANTE%TYPE) IS R_ESTUDIANTE ESTUDIANTE%ROWTYPE; R_CURSO CURSO%ROWTYPE; R_ESTUDIANTE_EVALUACION ESTUDIANTE_EVALUACION%ROWTYPE; BEGIN SELECT * INTO R_ESTUDIANTE FROM ESTUDIANTE WHERE DOCUMENTO_ESTUDIANTE LIKE VDOCUMENTO_ESTUDIANTE; PL('--- INFORMACION SOLICITADA DEL ESTUDIANTE ---'); PL('Nombre: '||R_ESTUDIANTE.NOMBRE_ESTUDIANTE||' Apellido: '||R_ESTUDIANTE.APELLIDO_ESTUDIANTE||' Documento: '||

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 96: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 96 de 121

R_ESTUDIANTE.DOCUMENTO_ESTUDIANTE); FOR R_INSCRITOEN IN C_INSCRITOEN(R_ESTUDIANTE.ID_ESTUDIANTE) LOOP SELECT * INTO R_CURSO FROM CURSO WHERE ID_CURSO = R_INSCRITOEN.ID_CURSO; PL('Inscrito en: '||R_CURSO.NOMBRE_CURSO); FOR R_EVALUACIONES IN C_EVALUACIONES(R_INSCRITOEN.ID_CURSO) LOOP SELECT * INTO R_ESTUDIANTE_EVALUACION FROM ESTUDIANTE_EVALUACION WHERE ID_ESTUDIANTE = R_ESTUDIANTE.ID_ESTUDIANTE AND ID_EVALUACION = R_EVALUACIONES.ID_EVALUACION; PL('Evaluacion: '||R_EVALUACIONES.ID_EVALUACION||' Calificacion Obtenida: '||R_ESTUDIANTE_EVALUACION.CALIFICACION); END LOOP; PL('Nota Media Obtenida: '||to_char(NOTA_MEDIA_ESTUDIANTE(R_ESTUDIANTE.ID_ESTUDIANTE,R_INSCRITOEN.ID_CURSO))); END LOOP; PL('Nota Maxima General Obtenida: '||to_char(NOTA_MAXIMA_ESTUDIANTE(R_ESTUDIANTE.ID_ESTUDIANTE))); PL('Nota Minima General Obtenida: '||to_char(NOTA_MINIMA_ESTUDIANTE(R_ESTUDIANTE.ID_ESTUDIANTE))); EXCEPTION WHEN NO_DATA_FOUND THEN PL('NO EXISTE UN ALUMNO CON ESE DOCUMENTO'); END INFO_ESTUDIANTE;

/* PROCEDURE HELP IS BEGIN END HELP;

PROCEDURE TEST IS BEGIN END TEST; */ END COURSES; . /

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 97: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 97 de 121

7.6 For Update: 

En una operación SELECT no se bloquean (lock) las filas seleccionadas – pues se trata de una operación de consulta – esto quiere decir que mientras un usuario está leyendo la información almacenada en una o varias tablas, otro usuario puede estar modificando la misma. Para el ejemplo desarrollado anteriormente un usuario podría estar leyendo el informe de un alumno o de un curso, mientras que otro usuario está actualizando esta misma información, de forma que cuando el primero solicite de nuevo la información verá los cambios suministrados por el segundo. 

Hasta el momento los Cursores expuestos han trabajado con consultas, pero ¿qué ocurre si se desea introducir una función que actualice todos los registros recuperados por un cursor? en ese caso se hace indispensable que la información recuperada quede bloqueada (lock) hasta que la operación de actualización termine, evitando así que un segundo usuario realice modificaciones indeseadas.

Ejemplo 1: Escriba un procedimiento de PL/SQL e intégrelo al paquete COURSES desarrollado anteriormente, que actualice las calificaciones de los estudiantes inscritos en un curso incrementándolas en 1 siempre y cuando su valor – luego de ser incrementadas – no sea mayor a 5.

Solución Propuesta:

Archivo Spec:

PROCEDURE SUBIR_CALIFICACIONES(VID_CURSO IN CURSO.ID_CURSO%TYPE);

Archivo Body:

PROCEDURE SUBIR_CALIFICACIONES(VID_CURSO IN CURSO.ID_CURSO%TYPE) --observe la definicion del cursor que se emplea en este procedimiento IS CURSOR C_CALIFICACIONES_FU(CVID_EVALUACION IN ESTUDIANTE_EVALUACION.ID_EVALUACION%TYPE) IS--variable local SELECT * FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = CVID_EVALUACION FOR UPDATE; --al incluir For Update en el Cursor se establece un bloqueo sobre los registros recuperados BEGIN FOR R_EVALUACIONES IN C_EVALUACIONES(VID_CURSO) LOOP --C_EVALUACIONES ES VARIABLE GLOBAL

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 98: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 98 de 121

FOR R_CALIFICACIONES_FU IN C_CALIFICACIONES_FU(R_EVALUACIONES.ID_EVALUACION) LOOP IF R_CALIFICACIONES_FU.CALIFICACION <= 4 THEN UPDATE ESTUDIANTE_EVALUACION SET CALIFICACION = R_CALIFICACIONES_FU.CALIFICACION + 1 WHERE ID_EVALUACION = R_CALIFICACIONES_FU.ID_EVALUACION AND ID_ESTUDIANTE = R_CALIFICACIONES_FU.ID_ESTUDIANTE; END IF; END LOOP; END LOOP; COMMIT; -- Luego del commit el bloqueo sobre los registros termina y otros usuarios/programas pueden modificarlos EXCEPTION WHEN NO_DATA_FOUND THEN PL('NO EXISTE UN CURSO CON ESE CODIGO'); END;

Test Unit (una imagen vale mas que mil palabras):

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 99: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 99 de 121

El mismo procedimiento puede escribirse de la siguiente manera:

Spec:

PROCEDURE SUBIR_CALIFICACIONES2(VID_CURSO IN CURSO.ID_CURSO%TYPE);

Body: 

PROCEDURE SUBIR_CALIFICACIONES2(VID_CURSO IN CURSO.ID_CURSO%TYPE) --observe la definicion del cursor que se emplea en este procedimiento IS CURSOR C_CALIFICACIONES_FU(CVID_EVALUACION IN ESTUDIANTE_EVALUACION.ID_EVALUACION%TYPE) IS--variable local SELECT * FROM ESTUDIANTE_EVALUACION WHERE ID_EVALUACION = CVID_EVALUACION FOR UPDATE OF CALIFICACION; --al incluir For Update Of se bloquea solo el campo especificado BEGIN FOR R_EVALUACIONES IN C_EVALUACIONES(VID_CURSO) LOOP --C_EVALUACIONES ES VARIABLE GLOBAL FOR R_CALIFICACIONES_FU IN C_CALIFICACIONES_FU(R_EVALUACIONES.ID_EVALUACION) LOOP IF R_CALIFICACIONES_FU.CALIFICACION <= 4 THEN UPDATE ESTUDIANTE_EVALUACION SET CALIFICACION = R_CALIFICACIONES_FU.CALIFICACION + 1 WHERE CURRENT OF C_CALIFICACIONES_FU; -- Where Current Of solo puede usarse con For Update Of END IF; END LOOP; END LOOP;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 100: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 100 de 121

COMMIT; -- Luego del commit el bloqueo sobre los registros termina y otros usuarios/programas pueden modificarlos EXCEPTION WHEN NO_DATA_FOUND THEN PL('NO EXISTE UN CURSO CON ESE CODIGO'); END;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 101: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 101 de 121

8. De vuelta a los Triggers: 

Hace algún tiempo se habló de Triggers en este documento, ahora con las bases adquiridas puede retomarse el tema y ahondarse en el mismo. Recuerde que un trigger es un bloque de código PL/SQL que se encuentra almacenado en la base de datos y que se ejecuta de forma automática cuando ocurren los siguientes eventos:

1. Eventos relacionados con DML34: INSERT, UPDATE, DELETE. Los Triggers definidos para estos eventos pueden ocurrir antes (before) o después (after) de que ocurran.

2. Eventos relacionados con DDL35: CREATE o ALTER. Este tipo de Triggers se emplea en auditorías de seguridad y generalmente se relacionan con usuarios o esquemas36 particulares de la base de datos.

3. Un evento del sistema como el Encendido o Apagado de la Base de Datos.4. Un evento de usuario como el acceso o la desconexión.

La sintaxis general para definir un Trigger es la siguiente:

CREATE [OR REPLACE] TRIGGER Trigger_name {BEFORE|AFTER} Triggering_event ON table_name [FOR EACH ROW] [WHEN condition] DECLARE declaration statements BEGIN executable statements EXCEPTION exception-handling statements END;

Para habilitar un Trigger se emplea la siguiente instrucción:

ALTER TRIGGER trigger_name ENABLE;

De manera similar para deshabilitar un Trigger se emplea la siguiente instrucción:

ALTER TRIGGER trigger_name DISABLE;

34 Data Manipulation Language35 Data Definition Language36 A schema is a collection of database objects. A schema is owned by a database user and has the same name as that user.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 102: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 102 de 121

Si se desea asociar más de un trigger a la misma tabla es posible emplear la cláusula FOLLOWS para indicar el orden en que deben ejecutarse los Triggers37:

ALTER TRIGGER trigger_name FOLLOWS trigger_name238;

Ejemplo 1: Defina un trigger que impida la creación de un estudiante con el nombre system39

Solución Propuesta:

CREATE OR REPLACE TRIGGER AVOID_RESERVED_NAMES BEFORE INSERT ON ESTUDIANTE FOR EACH ROW

DECLARE MSG VARCHAR2(50) := 'No esta permitido crear estudiantes con ese nombre'; BEGIN IF(UPPER(:NEW.NOMBRE_ESTUDIANTE) = 'SYSTEM') THEN RAISE_APPLICATION_ERROR(-20001,MSG); END IF; --EXCEPTION END AVOID_RESERVED_NAMES; . /

Cuando se intenta insertar un registro con ese nombre de usuario el sistema muestra un error como el siguiente:

INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'999999','SYSTEM','123');

VALUES(SEQ_ESTUDIANTE.NEXTVAL,'999999','SYSTEM','123') * ERROR at line 2: ORA-20001: No esta permitido crear estudiantes con ese nombre ORA-06512: at "SYSTEM.AVOID_RESERVED_NAMES", line 5 ORA-04088: error during execution of trigger 'SYSTEM.AVOID_RESERVED_NAMES'

Ahora que se ha cubierto el tema de Records puede explicarse que :NEW es en realidad una especie de Record (pseudorecord) del mismo tipo de la tabla a la que se encuentra asociado el Trigger.

37 De otra manera Oracle puede disparar los Triggers en cualquier orden.38 Se asume que los dos Triggers han sido definidos para trabajar sobre la misma tabla.39 Aunque es un ejemplo en la práctica se definen restricciones sobre los nombres de usuario que pueden almacenarse.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 103: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 103 de 121

Ejemplo 2: defina un Trigger que impida la actualización de una calificación empleando un valor menor al existente.

Solución Propuesta:

CREATE OR REPLACE TRIGGER AVOID_INCORRECT_GRADES_UPDATES BEFORE UPDATE ON ESTUDIANTE_EVALUACION FOR EACH ROW

DECLARE MSG VARCHAR2(100) := 'No es posible modificar la calificacion por una de menor valor'; BEGIN IF(:NEW.CALIFICACION < :OLD.CALIFICACION) THEN RAISE_APPLICATION_ERROR(-20001,MSG); END IF; --EXCEPTION END AVOID_INCORRECT_GRADES_UPDATES; . /

Observe el uso de :OLD en el ejemplo anterior, al igual que :NEW es un pseudorecord del mismo tipo que la tabla a la que se está asociando el Trigger. Es importante tener en cuenta que :OLD corresponde a la información que actualmente tiene el registro, mientras que :NEW corresponde a la información que está intentando ingresarse al registro.

Ejemplo 3: Con la ayuda de una nueva tabla denominada ESTADISTICA con los siguientes campos: ID_ESTADISTICA(NUMBER), TIPO_TRANSACCION(VARCHAR2 20), USUARIO(VARCHAR2 20), FECHA(DATE). Escriba un Trigger que lleve un registro de las operaciones realizadas sobre la tabla ESTUDIANTE_EVALUACION, de tal manera que en cualquier momento sea posible determinar quien modificó los registros de esta tabla (actualizándolos o eliminándolos) y en que fecha lo hizo.

Solución Propuesta:

DROP TABLE ESTADISTICA;

CREATE TABLE ESTADISTICA (ID_ESTADISTICA NUMBER, TIPO_TRANSACCION VARCHAR2(20), USUARIO VARCHAR2(20), FECHA DATE);

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 104: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 104 de 121

DROP SEQUENCE SEQ_ESTADISTICA;

CREATE SEQUENCE SEQ_ESTADISTICA MINVALUE 1 START WITH 1 INCREMENT BY 1 NOCACHE;

CREATE OR REPLACE TRIGGER AUDIT_ESTUDIANTE_EVALUACION AFTER UPDATE OR DELETE ON ESTUDIANTE_EVALUACION FOR EACH ROW DECLARE V_TIPO VARCHAR2(20); BEGIN IF UPDATING THEN V_TIPO := 'UPDATE'; ELSIF DELETING THEN V_TIPO := 'DELETE'; END IF; INSERT INTO ESTADISTICA VALUES(SEQ_ESTADISTICA.NEXTVAL, V_TIPO, USER, SYSDATE); END AUDIT_ESTUDIANTE_EVALUACION; . /

Unidad de Prueba:

Se realiza la siguiente actualización en el sistema:

UPDATE ESTUDIANTE_EVALUACION SET CALIFICACION=CALIFICACION+0.5 WHERE ID_ESTUDIANTE=3 AND ID_EVALUACION=15;

Ahora al consultar la tabla ESTADISTICA se obtiene la información relacionada con esta operación.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 105: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 105 de 121

Ejercicio 1: defina un Trigger que guarde en la tabla ESTADISTICA previamente creada la información relacionada con las operaciones: UPDATE, DELETE que se realizan sobre la tabla ESTUDIANTE. Modifique la tabla ESTADISTICA para que almacene el nombre de la tabla sobre la que el usuario realizó la operación.

Solución Propuesta:

TRUNCATE TABLE ESTADISTICA;

ALTER TABLE ESTADISTICA ADD (NOMBRE_TABLA VARCHAR2(30));

CREATE OR REPLACE TRIGGER AUDIT_ESTUDIANTE AFTER UPDATE OR DELETE ON ESTUDIANTE FOR EACH ROW DECLARE V_TIPO VARCHAR2(20); V_TABLA VARCHAR2(30); BEGIN IF UPDATING THEN V_TIPO := 'UPDATE'; V_TABLA := 'ESTUDIANTE'; ELSIF DELETING THEN V_TIPO := 'DELETE'; V_TABLA := 'ESTUDIANTE'; END IF; INSERT INTO ESTADISTICA VALUES(SEQ_ESTADISTICA.NEXTVAL, V_TIPO, USER, SYSDATE, V_TABLA); END AUDIT_ESTUDIANTE; . /

Ejercicio 2: defina un Trigger que guarde en la tabla ESTADISTICA previamente modificada la información relacionada con la operación INSERT que se realiza sobre la tabla ESTUDIANTE. Este Trigger debe dispararse DESPUES de que la operación de INSERCION se realiza.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 106: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 106 de 121

Solución Propuesta:

CREATE OR REPLACE TRIGGER AUDIT_ESTUDIANTE2 AFTER INSERT ON ESTUDIANTE FOR EACH ROW DECLARE V_TIPO VARCHAR2(20); V_TABLA VARCHAR2(30); BEGIN V_TIPO := 'INSERT'; V_TABLA := 'ESTUDIANTE'; INSERT INTO ESTADISTICA VALUES(SEQ_ESTADISTICA.NEXTVAL, V_TIPO, USER, SYSDATE, V_TABLA); END AUDIT_ESTUDIANTE2; . /

8.1 PRAGMA AUTONOMOUS_TRANSACTION: 

Es posible llevar los Triggers un paso más adelante incluyendo en ellos las denominadas Transacciones Autónomas, que no son otra cosa que transacciones que se efectúan como respuesta a otras transacciones. Considérese el ejemplo anterior, ¿qué ocurriría si la operación de inserción falla? ¿se perdería el registro de auditoría?

Las siguientes imágenes ilustran mejor lo que se quiere decir:

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 107: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 107 de 121

De esta manera el Trigger que actualmente está definido no llevaría un registro de los intentos fallidos de inserción de registros; algo que en muchos casos es sumamente importante dependiendo del tipo de auditoría que se esté realizando. Puede modificarse el Trigger anterior para que independientemente del resultado de la operación de Inserción lleve un registro de la misma.

CREATE OR REPLACE TRIGGER AUDIT_ESTUDIANTE2 BEFORE40 INSERT ON ESTUDIANTE FOR EACH ROW DECLARE V_TIPO VARCHAR2(20); V_TABLA VARCHAR2(30); PRAGMA AUTONOMOUS_TRANSACTION;41 BEGIN V_TIPO := 'INSERT'; V_TABLA := 'ESTUDIANTE';

40 Es importante modificar el Trigger para que se active antes de la inserción, de lo contrario el hecho de que la operación no se pueda realizar hará que el Trigger no se dispare

41 Sin esta declaración el Trigger no grabará nada en la tabla ESTADISTICA así se haya modificado el momento de ejecución del mismo

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 108: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 108 de 121

INSERT INTO ESTADISTICA VALUES(SEQ_ESTADISTICA.NEXTVAL, V_TIPO, USER, SYSDATE, V_TABLA); COMMIT; END AUDIT_ESTUDIANTE2; . /

Ahora la ejecución de una operación como: INSERT INTO ESTUDIANTE VALUES(SEQ_ESTUDIANTE.NEXTVAL,'98009','SYSTEM','123'); genera un nuevo registro en la tabla ESTADISTICA sin importar que la operación de Inserción en si falle – debido al Trigger que impide la inserción de registros con el nombre de usuario SYSTEM­.

Ejercicio 1: modifique la tabla ESTADISTICA para que almacene junto con la fecha la hora en la que se produjo la modificación – o el intento de – sobre las tablas en las cuales se está realizando la auditoría.

Solución Propuesta:

TRUNCATE TABLE ESTADISTICA;

ALTER TABLE ESTADISTICA MODIFY (FECHA TIMESTAMP); /

8.2 INSTEAD OF Trigger: 

Otra clase de Triggers son los que en lugar de reaccionar – o dispararse – antes o después de un evento específico, sustituyen al evento en sí. Esta clase de Triggers se emplean usualmente en las Vistas.

Ejemplo 1: Defina una vista para la tabla Estudiante – en realidad no existirá diferencia entre la tabla y la vista42­ y luego cree un INSTEAD OF Trigger que sustituya la operación de INSERT para la vista ESTUDIDANTE_VIEW y se encargue de almacenar en Mayúsculas el nombre y el apellido del estudiante en el nuevo registro.

42 Se crea la vista en este ejemplo para poder presentar el concepto de los Instead Of Triggers ya que los mismos no pueden crearse asociados a las Tablas

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 109: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 109 de 121

Solución Propuesta:

CREATE OR REPLACE VIEW ESTUDIANTE_VIEW AS SELECT * FROM ESTUDIANTE;

CREATE OR REPLACE TRIGGER TRG_INSERT_ESTUDIANTE INSTEAD OF INSERT ON ESTUDIANTE_VIEW FOR EACH ROW BEGIN INSERT INTO ESTUDIANTE VALUES (SEQ_ESTUDIANTE.NEXTVAL,:NEW.DOCUMENTO_ESTUDIANTE,UPPER(:NEW.NOMBRE_ESTUDIANTE),UPPER(:NEW.APELLIDO_ESTUDIANTE)); END TRG_INSERT_ESTUDIANTE; . /

Inserte un nuevo registro en la tabla:

INSERT INTO ESTUDIANTE_VIEW VALUES(1,'88998899','Julio','peres');

Consulte la tabla. Ahora entiende el funcionamiento de un Trigger del tipo Instead Of.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 110: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 110 de 121

9. Colecciones: 

Al comienzo de este curso se trabajaba recuperando un registro a la vez, luego gracias a la introducción de los Cursores y los Records fue posible trabajar sobre grupos de registros – recuperando un registro a la vez ­, las colecciones amplían las posibilidades del programador de PL/SQL permitiéndole recuperar múltiples filas a la vez y almacenándolas en una colección de manera similar a como lo haría en un Array en cualquier lenguaje de programación moderno.

9.1 Tablas PL/SQL: 

El primer tipo de colección que se cubrirá se denomina Tabla PL/SQL. Una tabla PL/SQL es similar a una tabla en la base de datos Oracle pero con una sola columna. Existen dos tipos de Tablas PL/SQL: Asociativas (también denominadas Index By Tables) y las Tablas Anidadas (Nested).

9.1.1 Tablas Index By: una Tabla Asociativa (Index By) se define de la siguiente manera:

TYPE type_name IS TABLE OF element_type [NOT NULL] INDEX BY element_type; table_name TYPE_NAME;

Ejemplo1: defina – dentro de un bloque PL/SQL ­ una tabla Index By que almacene los Documentos de los Estudiantes existentes en la base de datos desarrollada en la Clase 8.

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TABLA_APELLIDOS_TIPO IS TABLE OF ESTUDIANTE.APELLIDO_ESTUDIANTE%TYPE INDEX BY BINARY_INTEGER; TABLA_APELLIDOS TABLA_APELLIDOS_TIPO;

Observe como primero se define el tipo de dato que se almacenará en la tabla – en este caso el mismo tipo de dato empleado para almacenar los apellidos de los estudiantes en la tabla estudiante – y luego se crea una tabla de ese tipo.

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 111: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 111 de 121

La línea INDEX BY especifica el tipo de dato que se empleará como índice de la tabla. Esto se entiende mejor si se “visualiza” la tabla:

TABLA_APELLIDOS

  

Imagine que en cada una de las filas azules será almacenado un apellido, usted debe contar con un índice que le permita acceder a las diferentes filas, para el caso de este ejemplo se está definiendo un índice del tipo BINARY_INTEGER que no es otra cosa que un entero que va desde ­2147483647 hasta 2147483647.

Ejemplo 2: complemente el ejemplo 1 para que la tabla tipo Index By definida almacene los Apellidos de los Estudiantes

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TABLA_APELLIDOS_TIPO IS TABLE OF ESTUDIANTE.APELLIDO_ESTUDIANTE%TYPE INDEX BY BINARY_INTEGER; TABLA_APELLIDOS TABLA_APELLIDOS_TIPO; INDICE_TABLA BINARY_INTEGER := 0; BEGIN FOR R_ESTUDIANTE IN C_ESTUDIANTE LOOP INDICE_TABLA := INDICE_TABLA + 1 ; TABLA_APELLIDOS(INDICE_TABLA) := R_ESTUDIANTE.APELLIDO_ESTUDIANTE; END LOOP; END;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 112: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 112 de 121

Ejemplo 3: complemente el ejemplo 2 para imprimir cada una de las filas almacenadas en la tabla TABLA_APELLIDOS.

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TABLA_APELLIDOS_TIPO IS TABLE OF ESTUDIANTE.APELLIDO_ESTUDIANTE%TYPE INDEX BY BINARY_INTEGER; TABLA_APELLIDOS TABLA_APELLIDOS_TIPO; INDICE_TABLA BINARY_INTEGER := 0; BEGIN FOR R_ESTUDIANTE IN C_ESTUDIANTE LOOP INDICE_TABLA := INDICE_TABLA + 1; TABLA_APELLIDOS(INDICE_TABLA) := R_ESTUDIANTE.APELLIDO_ESTUDIANTE; END LOOP; --IMPRIMIR EL CONTENIDO DE LA TABLA INDEX BY FOR i IN 1..INDICE_TABLA LOOP PL(TABLA_APELLIDOS(i)); END LOOP; END; . /

Las tablas Index By generan una excepción tipo DATA_NO_FOUND cuando intenta accederse a un valor del índice no definido. Considere el siguiente cambio en el código anterior:

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TABLA_APELLIDOS_TIPO IS TABLE OF ESTUDIANTE.APELLIDO_ESTUDIANTE%TYPE INDEX BY BINARY_INTEGER; TABLA_APELLIDOS TABLA_APELLIDOS_TIPO; INDICE_TABLA BINARY_INTEGER := 1;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 113: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 113 de 121

BEGIN FOR R_ESTUDIANTE IN C_ESTUDIANTE LOOP TABLA_APELLIDOS(INDICE_TABLA) := R_ESTUDIANTE.APELLIDO_ESTUDIANTE; INDICE_TABLA := INDICE_TABLA + 1; END LOOP; --IMPRIMIR EL CONTENIDO DE LA TABLA INDEX BY FOR i IN 1..INDICE_TABLA LOOP PL(TABLA_APELLIDOS(i)); END LOOP; END; . /

Al momento de ejecutar este código aparece el siguiente error: ORA-01403: no data found, la razón es que el índice de la tabla fue incrementado hasta un valor mayor al número de filas que contiene. Observe como para cada apellido que se guarda se realiza un incremento en el valor del índice, esto implica que al momento de almacenar el último apellido existente el índice será incrementado nuevamente, un valor por encima del número de registros real. Al momento de imprimir los apellidos el Loop definido llegará hasta el último valor del índice y cuando intente imprimir dicho apellido, encontrará que no existen datos en él generando la excepción previamente mencionada.

9.1.2 Tablas Anidadas: una tabla anidada es muy parecida a una tabla del tipo Index By, pero a diferencia de éstas no lleva el Index By. La sintaxis para definirla es la siguiente:

TYPE type_name IS TABLE OF element_type [NOT NULL]; table_name TYPE_NAME; 

Otro aspecto que se debe tener en cuenta al momento de trabajar con Tablas Anidadas es que éstas deben inicializarse antes de poder guardar información en ellas.

Ejemplo 4: reescriba el Ejemplo 3 empleando una Tabla Anidada en lugar de una Tabla Asociativa.

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TABLA_APELLIDOS_TIPO IS TABLE OF ESTUDIANTE.APELLIDO_ESTUDIANTE%TYPE;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 114: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 114 de 121

TABLA_APELLIDOS TABLA_APELLIDOS_TIPO := TABLA_APELLIDOS_TIPO();--inicializacion de la tabla INDICE_TABLA BINARY_INTEGER := 0; BEGIN FOR R_ESTUDIANTE IN C_ESTUDIANTE LOOP INDICE_TABLA := INDICE_TABLA + 1; TABLA_APELLIDOS.EXTEND();--se incrementa el tamaño de la tabla TABLA_APELLIDOS(INDICE_TABLA) := R_ESTUDIANTE.APELLIDO_ESTUDIANTE; END LOOP; --IMPRIMIR EL CONTENIDO DE LA TABLA INDEX BY FOR i IN 1..INDICE_TABLA LOOP PL(TABLA_APELLIDOS(i)); END LOOP; END; . /

9.2 Métodos de las Colecciones: 

En el ejemplo anterior se utilizó el método EXTEND de la tabla anidada para ir incrementando el tamaño de la misma a cada paso dentro del Loop. Las colecciones cuentan con los siguientes métodos para su manipulación:

Nombre Descripción Disponible en

EXISTS Devuelve TRUE si el elemento especificado existe en la colección

Index By / Nested

COUNT Devuelve el número de elementos de una colección

Index By / Nested

EXTEND Incrementa en 1 el tamaño de la colección

Nested

DELETE Elimina uno o varios elementos de una colección. Las filas donde antes se encontraban esos elementos quedan desocupadas pero 

Index By / Nested

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 115: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 115 de 121

libres para ser utilizadas después

FIRST Devuelve el primer elemento de una colección

Index By / Nested

LAST Devuelve el último elemento de una colección

Index By / Nested

PRIOR Devuelve el índice del elemento n­1 a una posición n específica

Index By / Nested

NEXT Devuelve el índice del elemento n+1 a una posición n específica

Index By / Nested

TRIM Elimina uno o más elementos del final de una colección, las posiciones que antes ocupaban estos elementos también son eliminadas

Nested

Ejemplo 5: En un restaurante de comidas rápidas se encuentra un grupo de personas haciendo fila en espera de ser atendidas, la configuración de la cola es la siguiente:

Donde Luis ocupa el primer puesto y Pedro se encuentra en la última posición.

Escriba el código PL/SQL que recree esta situación empleando una Tabla Anidada, imprima el contenido de la tabla para su verificación:

DECLARE TYPE PERSONA IS TABLE OF VARCHAR2(20); COLA_RESTAURANTE PERSONA := PERSONA(); --INICIALIZAR TABLA POSICION BINARY_INTEGER := 0;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 116: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 116 de 121

BEGIN POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Luis'; --El primero en la cola POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Adriana'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Jose'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Maria'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Pedro'; --El ultimo en la cola FOR i IN 1..POSICION LOOP PL('Puesto: '||i||' '||COLA_RESTAURANTE(i)); END LOOP; END; . /

Ejercicio 1: Empleando los métodos PRIOR y NEXT de las colecciones imprima en pantalla la información sobre las personas que se encuentran por delante y por detrás de Jose, utilice el código anterior como punto de partida.

Solución Propuesta:

DECLARE TYPE PERSONA IS TABLE OF VARCHAR2(20); COLA_RESTAURANTE PERSONA := PERSONA(); --INICIALIZAR TABLA POSICION BINARY_INTEGER := 0; BEGIN POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Luis'; --El primero en la cola POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND();

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 117: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 117 de 121

COLA_RESTAURANTE(POSICION) := 'Adriana'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Jose'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Maria'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Pedro'; --El ultimo en la cola FOR i IN 1..POSICION LOOP PL('Puesto: '||i||' '||COLA_RESTAURANTE(i)); END LOOP; PL('Adelante de Jose se encuentra: '||COLA_RESTAURANTE(COLA_RESTAURANTE.PRIOR(3))); PL('Detras de Jose se encuentra: '||COLA_RESTAURANTE(COLA_RESTAURANTE.NEXT(3))); END; . /

Ejercicio 2: Empleando los métodos FIRST y LAST de las colecciones imprima en pantalla la información sobre las personas que se encuentran en la primera y en la última posición de la cola, utilice el código anterior como punto de partida.

Solución Propuesta:

DECLARE TYPE PERSONA IS TABLE OF VARCHAR2(20); COLA_RESTAURANTE PERSONA := PERSONA(); --INICIALIZAR TABLA POSICION BINARY_INTEGER := 0; BEGIN POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Luis'; --El primero en la cola POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Adriana';

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 118: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 118 de 121

POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Jose'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Maria'; POSICION := POSICION + 1; COLA_RESTAURANTE.EXTEND(); COLA_RESTAURANTE(POSICION) := 'Pedro'; --El ultimo en la cola FOR i IN 1..POSICION LOOP PL('Puesto: '||i||' '||COLA_RESTAURANTE(i)); END LOOP; PL('Adelante de Jose se encuentra: '||COLA_RESTAURANTE(COLA_RESTAURANTE.PRIOR(3))); PL('Detras de Jose se encuentra: '||COLA_RESTAURANTE(COLA_RESTAURANTE.NEXT(3))); PL('El primero en la cola es: '||COLA_RESTAURANTE(COLA_RESTAURANTE.FIRST())); PL('El ultimo en la cola es: '||COLA_RESTAURANTE(COLA_RESTAURANTE.LAST())); END; . /

9.3 Tablas de registros: 

El concepto expuesto anteriormente puede extenderse de la siguiente manera:

TYPE TABLA_ESTUDIANTES_TIPO IS TABLE OF ESTUDIANTE.%ROWTYPE INDEX BY BINARY_INTEGER; TABLA_APELLIDOS TABLA_ESTUDIANTES_TIPO;

La definición anterior permite entender las colecciones dentro del contexto en el que se ha venido trabajando y hacer uso de una técnica denominada Bulk Collect.

Es importante entender que cada vez que dentro de un programa PL/SQL se realizan consultas a la base de datos el control pasa del motor PL/SQL al propio motor SQL. Esto no es significativo si se está hablando de 15 o 20 registros, pero cuando se está hablando de un número mayor este ir y venir entre un motor y otro reduce considerablemente el desempeño 

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 119: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 119 de 121

de un programa PL/SQL.

Bulk Collect es una técnica que haciendo uso de las tablas PL/SQL permite tener dentro del motor PL/SQL la información requerida para trabajar dentro de un programa, función o procedimiento.

Ejemplo 1: utilizando una tabla Index By (asociativa) y Bulk Collect recupere los registros de los estudiantes existentes en la tabla ESTUDIANTE, imprima cada uno de los registros de la tabla.

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TIPO_ESTUDIANTE IS TABLE OF ESTUDIANTE%ROWTYPE INDEX BY BINARY_INTEGER; TABLA_ESTUDIANTES TIPO_ESTUDIANTE; V_CONTADOR BINARY_INTEGER := 0; BEGIN OPEN C_ESTUDIANTE; FETCH C_ESTUDIANTE BULK COLLECT INTO TABLA_ESTUDIANTES; FOR i IN 1..TABLA_ESTUDIANTES.COUNT() LOOP PL(TABLA_ESTUDIANTES(i).ID_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).DOCUMENTO_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).NOMBRE_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).APELLIDO_ESTUDIANTE); END LOOP; END; . /

Es importante tener en cuenta que BULK COLLECT puede aumentar el rendimiento de los programas PL/SQL siempre y cuando el número de registros que se almacenen en memoria del motor PL/SQL no sea demasiado grande, por lo que para trabajar con tablas muy grandes se recomienda usar LIMIT N como un condicional de la selección:

DECLARE CURSOR C_ESTUDIANTE IS

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 120: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 120 de 121

SELECT * FROM ESTUDIANTE; TYPE TIPO_ESTUDIANTE IS TABLE OF ESTUDIANTE%ROWTYPE INDEX BY BINARY_INTEGER; TABLA_ESTUDIANTES TIPO_ESTUDIANTE; V_CONTADOR BINARY_INTEGER := 0; BEGIN OPEN C_ESTUDIANTE; FETCH C_ESTUDIANTE BULK COLLECT INTO TABLA_ESTUDIANTES LIMIT 3; FOR i IN 1..TABLA_ESTUDIANTES.COUNT() LOOP PL(TABLA_ESTUDIANTES(i).ID_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).DOCUMENTO_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).NOMBRE_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).APELLIDO_ESTUDIANTE); END LOOP; END; . /

Ejercicio 1: compare la salida de los dos códigos presentados anteriormente. Incluya un Loop en el segundo para que imprima todos los estudiantes al mismo tiempo que realiza un manejo razonable de memoria utilizando el condicional LIMIT

Solución Propuesta:

DECLARE CURSOR C_ESTUDIANTE IS SELECT * FROM ESTUDIANTE; TYPE TIPO_ESTUDIANTE IS TABLE OF ESTUDIANTE%ROWTYPE INDEX BY BINARY_INTEGER; TABLA_ESTUDIANTES TIPO_ESTUDIANTE; V_CONTADOR BINARY_INTEGER := 0; BEGIN OPEN C_ESTUDIANTE;

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)

Page 121: Guia Curso-JAMS-Version Final (Recuperado)

Programación en Oracle con PL/SQL - 121 de 121

LOOP FETCH C_ESTUDIANTE BULK COLLECT INTO TABLA_ESTUDIANTES LIMIT 3; EXIT WHEN(C_ESTUDIANTE%NOTFOUND); FOR i IN 1..TABLA_ESTUDIANTES.COUNT() LOOP PL(TABLA_ESTUDIANTES(i).ID_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).DOCUMENTO_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).NOMBRE_ESTUDIANTE||CHR(9)||TABLA_ESTUDIANTES(i).APELLIDO_ESTUDIANTE); END LOOP; END LOOP; END; . /

Fin de los apuntes de clase

Apuntes de Clase por José Andrés Martínez Silva (http://jamslug.com)