Vous êtes sur la page 1sur 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)


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

Tabla de Contenidos
1. Revisión de Conceptos Básicos de SQL................................................................................................3
1.1 Primer ejemplo de programación con PL/SQL: Triggers..............................................................10
1.2 Consultas sobre la Información Almacenada:...............................................................................11
1.3 Vistas:............................................................................................................................................13
2. Introducción a PL/SQL........................................................................................................................15
2.1 Variables de sustitución:................................................................................................................17
2.2 Tipos de datos soportados en PL/SQL:.........................................................................................21
2.3 Bloques anidados y control de flujo:.............................................................................................22
2.4 Funciones: ....................................................................................................................................25
2.5 Adición de Secuencias a las Tablas:..............................................................................................27
2.6 Commit, Rollback y Savepoint:....................................................................................................30
2.7 Revisión Parcial de lo visto hasta este punto:...............................................................................32
3. Procedimientos Almacenados: ............................................................................................................36
3.1 Bloques de código anidados: ........................................................................................................37
4. Paquetes:..............................................................................................................................................38
4.1 Empleando CASE para controlar el flujo de un Programa: .........................................................46
4.2 CASE con condiciones de búsqueda: ...........................................................................................47
4.3 Control de Iteraciones con LOOP:................................................................................................48
4.3.1 Loop Simple:.........................................................................................................................48
4.3.2 WHILE LOOPS:....................................................................................................................50
4.3.3 FOR LOOP:...........................................................................................................................53
4.3.4 Loops Anidados:....................................................................................................................54
5. Parámetros de Entrada y Salida – Aclaración: ....................................................................................55
6. Manejo de Excepciones: .....................................................................................................................57
6.1 Excepciones Comunes Incluidas en Oracle:.................................................................................59
7. Cursores: .............................................................................................................................................62
7.1 Definición de un CURSOR explícito: ..........................................................................................64
7.1.1 Records: ................................................................................................................................64
7.2 Atributos de los CURSORES: ......................................................................................................67
7.3 Cursor For Loop: ..........................................................................................................................71
7.4 Parámetros para los Cursores: ......................................................................................................71
7.5 Cursores anidados: .......................................................................................................................73
7.6 For Update: ...................................................................................................................................88
8. De vuelta a los Triggers: .....................................................................................................................91
8.1 PRAGMA AUTONOMOUS_TRANSACTION: .........................................................................95
8.2 INSTEAD OF Trigger: .................................................................................................................98
9. Colecciones: ........................................................................................................................................99
9.1 Tablas PL/SQL: ............................................................................................................................99
9.1.1 Tablas Index By.....................................................................................................................99
9.1.2 Tablas Anidadas:..................................................................................................................102
9.2 Métodos de las Colecciones: ......................................................................................................103
9.3 Tablas de registros: .....................................................................................................................107

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


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)


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)


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)


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)


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)


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)


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)


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)


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)


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)


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_ALUMNO
FROM EXAMEN_ALUMNO EA JOIN
ALUMNO A
ON
EA.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)


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 AS


SELECT EA.ID_EXAMEN, EA.FECHA, EA.CALIFICACION, A.DOCUMENTO_ALUMNO,
A.NOMBRE_ALUMNO, A.APELLIDO_ALUMNO
FROM EXAMEN_ALUMNO EA JOIN
ALUMNO A
ON
EA.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_ALUMNO
SET CALIFICACION = 3
WHERE 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)


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_ALUMNO
SET CALIFICACION = 3.5
WHERE 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 ALUMNO


VALUES(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)


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ón
BEGIN 
   Sentencias de ejecución
EXCEPTION 
   Sentencias para el manejo de excepciones
END; 

Ú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

BEGIN
SELECT NOMBRE_ALUMNO, APELLIDO_ALUMNO
FROM ALUMNO
WHERE ID_ALUMNO = 1
END;

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)


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

Ejemplo 2:

DECLARE
aux_nombre varchar2(50);
aux_apellido varchar2(50);
BEGIN
SELECT NOMBRE_ALUMNO, APELLIDO_ALUMNO
INTO aux_nombre, aux_apellido
FROM ALUMNO
WHERE 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:

DECLARE
aux_nombre varchar2(50);
aux_apellido varchar2(50);
BEGIN
SELECT NOMBRE_ALUMNO, APELLIDO_ALUMNO
INTO aux_nombre, aux_apellido
FROM ALUMNO
WHERE 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)


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

Ejemplo 4:

DECLARE
aux_nombre varchar2(50);
aux_apellido varchar2(50);
BEGIN
SELECT NOMBRE_ALUMNO, APELLIDO_ALUMNO
INTO aux_nombre, aux_apellido
FROM ALUMNO
WHERE ID_ALUMNO = 12;
DBMS_OUTPUT.PUT_LINE ('Estudiante seleccionado (en altas): '||
upper(aux_nombre)||' '||upper(aux_apellido));
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_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

DECLARE
aux_hola varchar2(50) := 'Hola Mundo';
BEGIN
DBMS_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)


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

DECLARE
aux_hola varchar2(50) := 'Hola';
aux_nombre varchar2(50) := '&nombre_usuario';
BEGIN
DBMS_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;


DECLARE
aux_hola varchar2(50) := 'Hola';
aux_nombre varchar2(50) := '&nombre_usuario';
BEGIN
DBMS_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)


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)


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)


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)


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)


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)


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)


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)


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)


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)


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)


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)


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_leccion


MINVALUE 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)


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

START WITH 2
INCREMENT BY 1
NOCACHE

CREATE SEQUENCE seq_tema


MINVALUE 3
START WITH 3
INCREMENT 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)


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)


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)


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 Doe
4 Marcus Indigus
5 Roy Barnes
6 Louis Mint

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


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)


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)


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)


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)


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)


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)


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)


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)


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)


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
------------
0009
15 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)


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 generado
19 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_LINE


21 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)


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)


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)


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_number
LOOP
{.statements.}
END LOOP;

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


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)


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)


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)


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 tareas
1. 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 1
3. 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)


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)


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 ALEATORIO
2. defina una secuencia seq_prueba que comience en 1 y se incrementa en 1
3. 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)


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:
1
3

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


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

6
10
15
...

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)


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)


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 1
3. 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)


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)


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)


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)


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)


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)


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  Esta excepción NO ocurre 
cuando al realizar una  cuando se realiza un SELECT 
consulta (SELECT o SELECT  COUNT o SELECT SUM, 
INTO) no se obtiene ningún  porque cualquiera de estas 
valor dos funciones retorna un 0 en 
caso de no existir registros

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


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)


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)


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)


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)


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)


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

DECLARE
CURSOR 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)


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ífico
2. 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)


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)


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)


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)


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)


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)


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)


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 CURSOR
26 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)


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)


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 79899012
2. Rosa Hernandez 52780041
3. María Castro 53900789
4. Carlos Hernandez 80001000
5. Pedro Marin 79998901
4. Escriba el código SQL que inserte 5 cursos en la tabla correspondiente
1. Matematicas
2. Geometria
3. Biologia
4. Bases de Datos
5. 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)


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 1
2. La nota máxima es 5
3. 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 Curso
2. Alumnos Inscritos
3. Nota Media
4. Nota Mínima
5. 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 Materia
2. Para cada uno de los exámenes presentados en  ­ esa ­ materia:
1. Nota obtenida en el examen
3. 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)


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)


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 compuesta
30 Primera llave foránea de la tabla
31 Segunda llave foránea de la tabla

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


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)


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)


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)


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)


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)


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)


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)


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)


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)


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)


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)


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_CU
RSO)));
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)


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)


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)


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)


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)


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 Language


35 Data Definition Language
36 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)


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)


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)


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)


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)


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)


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)


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)


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_ESTUD
IANTE),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)


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)


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)


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)


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)


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  Index By / Nested
elemento especificado existe 
en la colección
COUNT Devuelve el número de  Index By / Nested
elementos de una colección
EXTEND Incrementa en 1 el tamaño de  Nested
la colección
DELETE Elimina uno o varios  Index By / Nested
elementos de una colección. 
Las filas donde antes se 
encontraban esos elementos 
quedan desocupadas pero 

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


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

libres para ser utilizadas 
después
FIRST Devuelve el primer elemento  Index By / Nested
de una colección
LAST Devuelve el último elemento  Index By / Nested
de una colección
PRIOR Devuelve el índice del  Index By / Nested
elemento n­1 a una posición n 
específica
NEXT Devuelve el índice del  Index By / Nested
elemento n+1 a una posición 
n específica
TRIM Elimina uno o más elementos  Nested
del final de una colección, las 
posiciones que antes 
ocupaban estos elementos 
también son eliminadas

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)


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)


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)


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)


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)


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)


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)

Vous aimerez peut-être aussi