Professional Documents
Culture Documents
Programación en Oracle con PL/SQL
Apuntes de Clase
por: José Andrés Martínez Silva
Elaborados en Febrero – Marzo del 2009
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
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
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
);
Ejercicio 2:
Escriba el código DDL que incluirá las restricciones necesarias para definir las llaves
primarias y foráneas
Solución Propuesta:
id_examen,
id_alumno
);
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.
Solución Propuesta:
Inserte ahora los siguientes datos en las tablas:
id_leccion fecha
1 17Feb09
2 18Feb09
id_examen descripcion_examen
1 Primera revision
id_examen id_leccion
1 1
Código sugerido (DDL):
VALUES(2,
'53783975',
'Paola',
'Hernandez'
);
)
VALUES(2,
'Modelamiento DB',
NULL,
1
);
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
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:
Ingrese ahora los siguiente registros empleando los códigos DDL correspondientes:
¿Cuáles son los códigos?
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:
Seleccionarlas usando el documento no el ID:
Otra forma de lograr el mismo resultado:
Pero cuál es la ventaja de emplear la segunda forma?
Modifique el código SQL anterior para lograr un primer “reporte”:
Debería obtener algo como lo siguiente:
Y si en lugar de la consulta anterior utiliza el siguiente código SQL:
Verá un reporte más adecuado:
Reporte sugerido en clase:
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
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:
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 18Feb09
(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:
Ejercicio 5:
Escriba el código SQL que permita actualiza la calificación de Juan Perez en el examen
presentado el 18Feb09 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:
Ejercicio 6:
Insertar un nuevo alumno con los siguientes datos: 4,11111,Alumno,Eliminado
Dicho alumno se puede eliminar con la instrucción:
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.
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.
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
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:
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
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.
Para evitar la salida en pantalla del valor original y el valor sustituido se modifica el código de
la siguiente manera:
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:
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:
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);
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:
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:
● 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 19Feb09
EXAMEN
id_examen descripcion_examen
2 2do examen
EXAMEN_LECCION:
id_examen id_leccion
2 2
2 3
EXAMEN_ALUMNO:
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 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:
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;
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 exceptionhandling section
END;
/
Aplicando esta sintaxis al ejercicio anterior:
IS
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;
END;
/
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:
Unidad de Prueba (Test Unit):
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
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:
Y ahora, para insertar un par de nuevos registros en la tabla, se emplean los siguientes
comandos SQL:
Ejercicio:
Cree las secuencias que permitan insertar nuevos registros en la tabla lección, examen y
tema.
START WITH 2
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:
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');
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 17FEB09
2 18FEB09
3 19FEB09
Inserte un registro para la fecha 23FEB09 – recuerde que sus tablas cuentan ahora con una
secuencia para la generación automática de la llave primaria:
Defina un punto de guardado SAVEPOINT A;
Inserte un nuevo registro para la fecha 24Feb09 y luego deshaga los últimos cambios
empleando: ROLLBACK TO A;
Qué información tiene la tabla 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?.
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
Solución Propuesta Puntos 1 y 2:
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
Solución Propuesta Punto 3:
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);
SAVEPOINT A;
DBMS_OUTPUT.PUT_LINE(v_name);
SAVEPOINT B;
DBMS_OUTPUT.PUT_LINE(v_name);
SAVEPOINT C;
SELECT ID
INTO v_id
FROM CHAP4 WHERE NAME LIKE 'Roy Barnes';
ROLLBACK TO B;
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;
DBMS_OUTPUT.PUT_LINE(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
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:
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 24character
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
--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
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.
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;
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;
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:
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:
El parámetro S utiliza el siguiente año al año especificado, en este caso -4713 en lugar de -4712
-- 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;
FUNCTION get_max
return date;
FUNCTION get_min
return date;
PROCEDURE help;
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;
PROCEDURE test;
end DATES;
/
@/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:
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
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:
LPAD(TO_CHAR
------------
2009
LPAD(TO_CHAR
------------
0009
15 Si ninguna excepción ha ocurrido el Loop termina en este punto
FUNCTION start_of_day(
aid_date in date )
return date is
begin
return trunc19(aid_date);
end start_of_day;
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:
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));
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;
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;
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
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
@/home/jamslug/par_o_impar_1.sql
Unidad de Prueba (Test Unit) para el ejemplo anterior:
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
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;
ELSE STATEMENT N+1;
END CASE;
Ejercicio 3: reescriba la función anterior empleando un CASE con condiciones de búsqueda.
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
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:
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
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:
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;
.
/
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:
ALEATORIO NUMBER);
DECLARE
v_n1 CONSTANT NUMBER := 1;
v_n2 CONSTANT NUMBER := 250;
v_aux NUMBER;
v_verify1 NUMBER;
v_verify2 NUMBER;
BEGIN
v_aux := 0;
SELECT COUNT(ID)
INTO v_verify2
FROM PRUEBA
WHERE ALEATORIO = v_verify1;
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
6
10
15
...
Solución Propuesta:
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;
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:
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:
VALUES(SEQ_SIMULA.NEXTVAL,aux_codigo,ROUND(DBMS_RANDOM.VALUE(1,5),2))
;
END LOOP; --loop interno
END LOOP; --loop externo
END;
.
/
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:
--TEST UNIT
PROCEDURE test;
END PARAMETERS;
.
/
Cuerpo del Paquete:
BEGIN
RETURN ('valor devuelto por la funcion: '||upper(ain_name));
END name_to_upper;
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));
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
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;
.
/
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
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:
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
Solución Propuesta:
--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;
--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 :
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;
--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;
.
/
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.
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);
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);
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:
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.
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.
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:
BEGIN
OPEN c_grades; --1. Abrir el Cursor
LOOP
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.
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.
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;
BEGIN
OPEN c_infoalumno; --1.Open 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;
.
/
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
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
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);
IF(c_infoalumno%ISOPEN) THEN
pl('Estado del Cursor: Open');
ELSE
pl('Estado del Cursor: Closed');
END IF;
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;
v_lastid NUMBER := 0;
BEGIN
pl('--- CALIFICACIONES DISPONIBLES PARA EL CODIGO: '||AIN_CODIGO);
IF(c_infoalumno%ISOPEN) THEN
pl('Estado del Cursor: Open');
ELSE
pl('Estado del Cursor: Closed');
END IF;
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
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:
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
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.
v_aux1 NUMBER := 0;
v_aux2 NUMBER := 0;
BEGIN
OPEN c_factorial(v_n1);--OPEN cursor + PARAM
FETCH c_factorial INTO v_aux1; --FECTH cursor
CLOSE c_factorial; --CLOSE CURSOR
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)
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
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:
--secuencias
DROP SEQUENCE SEQ_ESTUDIANTE;
DROP SEQUENCE SEQ_CURSO;
DROP SEQUENCE SEQ_EVALUACION;
Punto 3 y 4:
Punto 5:
DECLARE
V_MAT NUMBER := 0;
V_EST NUMBER := 0;
V_AUX NUMBER := 0;
V_AUX2 NUMBER := 0;
CURSOR C_MATERIA IS
SELECT ID_CURSO
FROM CURSO;
BEGIN
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error was encountered - '||
SQLCODE||' -ERROR- '||SQLERRM);
END;
.
/
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;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error was encountered - '||
SQLCODE||' -ERROR- '||SQLERRM);
END;
.
/
Punto 7:
Punto 8:
Spec. del Paquete:
FUNCTION NOTA_MINIMA_ESTUDIANTE(VID_ESTUDIANTE IN
ESTUDIANTE.ID_ESTUDIANTE%TYPE)
RETURN NUMBER;
FUNCTION NOTA_MAXIMA_ESTUDIANTE(VID_ESTUDIANTE IN
ESTUDIANTE.ID_ESTUDIANTE%TYPE)
RETURN NUMBER;
PROCEDURE INFO_ESTUDIANTE(VDOCUMENTO_ESTUDIANTE IN
ESTUDIANTE.DOCUMENTO_ESTUDIANTE%TYPE);
/*
PROCEDURE HELP32;
PROCEDURE TEST33;
*/
END COURSES;
.
/
Body del Paquete:
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.
R_EVALUACIONESEST2 C_EVALUACIONESEST2%ROWTYPE;
R_CALIFICACIONES1 CC_CALIFICACIONES1%ROWTYPE;
R_CALIFICACIONES2 CC_CALIFICACIONES2%ROWTYPE;
R_CALIFICACIONES3 CC_CALIFICACIONES3%ROWTYPE;
R_CALIFICACIONES4 CC_CALIFICACIONES4%ROWTYPE;
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);
END NOTA_MEDIA_CURSO;
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;
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;
END LOOP;
RETURN V_NOTA_MAXIMA;
END NOTA_MAXIMA_CURSO;
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);
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: '||
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;
.
/
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:
Archivo Body:
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 ID_EVALUACION = R_CALIFICACIONES_FU.ID_EVALUACION
AND ID_ESTUDIANTE = R_CALIFICACIONES_FU.ID_ESTUDIANTE;
END IF;
END LOOP;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
PL('NO EXISTE UN CURSO CON ESE CODIGO');
END;
Test Unit (una imagen vale mas que mil palabras):
El mismo procedimiento puede escribirse de la siguiente manera:
Spec:
Body:
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;
EXCEPTION
WHEN NO_DATA_FOUND THEN
PL('NO EXISTE UN CURSO CON ESE CODIGO');
END;
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:
Para habilitar un Trigger se emplea la siguiente instrucción:
De manera similar para deshabilitar un Trigger se emplea la siguiente instrucción:
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:
Ejemplo 1: Defina un trigger que impida la creación de un estudiante con el nombre system39
Solución Propuesta:
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:
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.
Ejemplo 2: defina un Trigger que impida la actualización de una calificación empleando un
valor menor al existente.
Solución Propuesta:
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:
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.
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:
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.
Solución Propuesta:
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:
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.
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:
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
Solución Propuesta:
Inserte un nuevo registro en la tabla:
Consulte la tabla. Ahora entiende el funcionamiento de un Trigger del tipo Instead Of.
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:
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;
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.
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;
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;
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;
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;
.
/
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;
TABLA_APELLIDOS TABLA_APELLIDOS_TIPO;
INDICE_TABLA BINARY_INTEGER := 1;
BEGIN
FOR R_ESTUDIANTE IN C_ESTUDIANTE LOOP
TABLA_APELLIDOS(INDICE_TABLA) := R_ESTUDIANTE.APELLIDO_ESTUDIANTE;
INDICE_TABLA := INDICE_TABLA + 1;
END LOOP;
END;
.
/
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;
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;
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:
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 n1 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);
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';
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
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);
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';
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
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);
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';
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
9.3 Tablas de registros:
El concepto expuesto anteriormente puede extenderse de la siguiente manera:
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
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;
TABLA_ESTUDIANTES TIPO_ESTUDIANTE;
V_CONTADOR BINARY_INTEGER := 0;
BEGIN
OPEN C_ESTUDIANTE;
FETCH C_ESTUDIANTE BULK COLLECT INTO TABLA_ESTUDIANTES;
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
TABLA_ESTUDIANTES TIPO_ESTUDIANTE;
V_CONTADOR BINARY_INTEGER := 0;
BEGIN
OPEN C_ESTUDIANTE;
FETCH C_ESTUDIANTE BULK COLLECT INTO TABLA_ESTUDIANTES LIMIT 3;
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;
TABLA_ESTUDIANTES TIPO_ESTUDIANTE;
V_CONTADOR BINARY_INTEGER := 0;
BEGIN
OPEN C_ESTUDIANTE;
LOOP
FETCH C_ESTUDIANTE BULK COLLECT INTO TABLA_ESTUDIANTES LIMIT 3;
EXIT WHEN(C_ESTUDIANTE%NOTFOUND);
END;
.
/
Fin de los apuntes de clase