BD-UT06-Programación de bases de datos
BD-UT06-Programación de bases de datos
Nombre completo
Bases de datos. Siglas MP BD
del MP
1. Introducción.
2. Conceptos básicos.
1. Unidades léxicas (I).
1. Unidades léxicas (II).
2. Tipos de datos simples, variables y constantes.
1. Subtipos.
2. Variables y constantes.
3. El bloque PL/SQL.
4. Estructuras de control (I).
1. Estructuras de control (II).
5. Excepciones.
1. Manejo de errores.
3. Tipos de datos compuestos.
1. Registros.
2. Colecciones. Arrays de longitud variable.
1. Colecciones. Tablas anidadas.
Índice o tabla de 3. Cursores.
contenidos 1. Cursores explícitos.
2. Cursores variables.
4. Abstracción en PL/SQL.
1. Subprogramas.
1. Almacenar subprogramas en la base de datos.
2. Parámetros de los subprogramas.
3. Sobrecarga de subprogramas y recursividad.
2. Paquetes.
1. Ejemplos de utilización del paquete DBMS_OUTPUT.
3. Objetos.
1. Objetos. Funciones mapa y funciones de orden.
5. Disparadores.
1. Definición de disparadores.
2. Ejemplos de disparadores.
6. Interfaces de programación de aplicaciones para lenguajes
externos.
7. Tutorial y ejercicios resueltos.
275
8. Enlaces de refuerzo y ampliación.
1.- Introducción.
Ahora que ya dominas el uso de SQL para la manipulación y consulta de datos, es el momento de dar
una vuelta de tuerca adicional para mejorar las aplicaciones que utilicen nuestra base de datos. Para
ello nos vamos a centrar en la programación de bases de datos, utilizando el lenguaje PL/SQL. En esta
unidad conoceremos qué es PL/SQL, cuál es su sintaxis y veremos cómo podemos sacarle el máximo
partido a nuestra base de datos mediante su uso.
Estarás pensado que, si no tenemos bastante con aprender SQL, sino que ahora tenemos que aprender
otro lenguaje más que lo único que va a hacer es complicarnos la vida. Verás que eso no es cierto ya
que lo más importante, que es el conocimiento de SQL, ya lo tienes. PL/SQL tiene una sintaxis muy
sencilla y verás cómo pronto te acostumbras y luego no podrás vivir sin él.
Pero, ¿qué es realmente PL/SQL?
276
Aunque PL/SQL fue creado por Oracle, hoy día todos los gestores de
bases de datos utilizan un lenguaje procedimental muy parecido al
ideado por Oracle para poder programar las bases de datos.
Una de las grandes ventajas que nos ofrece PL/SQL es un mejor rendimiento en entornos de red cliente-
servidor, ya que permite mandar bloques PL/SQL desde el cliente al servidor a través de la red,
reduciendo de esta forma el tráfico y así no tener que mandar una a una las sentencias SQL
correspondientes.
En esta unidad vamos a trabajar con el SGBD de Oracle. Por tanto, vamos a instalar Oracle Database
Express Edition 11g Release 2 tal y como se explica en el Anexo I: Primeros pasos en Oracle
Database que puedes encontrar en la primera unidad del módulo "Almacenamiento de la Información".
Además, como has podido ir viendo en las unidades anteriores se ha hecho referencia en todo momento
a las diferencias entre Oracle y MySQL. Si lo deseas, puedes cargar algún script de la unidad 4 para
Oracle y realizar algunos ejemplos en este sistema gestor de base de datos. En el tema que nos ocupa
de esta unidad encontrarás al final de la unidad un caso práctico donde puedes crear las tablas y datos
necesarios para practicar los ejemplos propuestos con PL/SQL.
Para trabajar con PL/SQL podemos trabajar directamente desde la aplicación gráfica de Oracle 11g, o
bien podemos instalar una nueva herramienta llamada SQL Developer que nos ayude a trabajar con
bloques PL/SQL. Dicha herramienta la podremos encontrar en la página oficial de Oracle. En el primer
vídeo se puede ver cómo se realiza la descarga y se abre la aplicación y en el segundo vídeo se
pueden seguir los primeros pasos con SQL Developer para conectarlo a una base de datos o espacio
de trabajo que tengamos creada en Oracle Database.
277
2.- Conceptos básicos.
En este apartado nos vamos a ir introduciendo poco a poco en los diferentes conceptos que debemos
tener claros para programar en PL/SQL. Como para cualquier otro lenguaje de programación, debemos
conocer las reglas de sintaxis que podemos utilizar, los diferentes elementos de que consta, los tipos
de datos de los que disponemos, las estructuras de control que nos ofrece (tanto iterativas como
condicionales) y cómo se realiza el manejo de los errores.
Como podrás comprobar, es todo muy sencillo y pronto estaremos escribiendo fragmentos de código
que realizan alguna tarea particular. ¡Vamos a ello!
Autoevaluación
Indica cuáles de las siguientes características que nos proporciona PL/SQL son ciertas.
Para utilizar PL/SQL debemos instalar diferentes drivers en nuestra base de datos Oracle.
Correcto
Cuando utilizamos PL/SQL no debemos instalar nada adicional en nuestra base de datos Oracle y podemos
hacer uso de las sentencias SQL dentro de un bloque PL/SQL.
PL/SQL es un lenguaje no sensible a las mayúsculas, por lo que será equivalente escribir en
mayúsculas o minúsculas, excepto cuando hablemos de literales de tipo cadena o de tipo carácter.
Cada unidad léxica puede estar separada por espacios (debe estar separada por espacios si se trata
de 2 identificadores), por saltos de línea o por tabuladores para aumentar la legibilidad del código
escrito.
278
Sería equivalente a escribir la siguiente línea:
IF a = clave THEN
encontrado := TRUE;
ELSE
encontrado := FALSE;
END IF;
Delimitadores.
Identificadores.
Literales.
Comentarios.
Delimitadores.
Delimitadores en PL/SQL.
+ Suma. ** Exponenciación.
. Selector. ¡= Distinto.
279
Delimitadores en PL/SQL.
; Terminador de sentencias.
- Resta/negación.
Ya hemos visto qué son los delimitadores. Ahora vamos a continuar viendo el resto de unidades léxicas
que nos podemos encontrar en PL/SQL.
Identificadores.
Los identificadores en PL/SQL, como en cualquier otro lenguaje de programación, son utilizados para
nombrar elementos de nuestros programas. A la hora de utilizar los identificadores debemos tener en
cuenta los siguientes aspectos:
Literales.
Los literales se utilizan en las comparaciones de valores o para asignar valores concretos a los
identificadores que actúan como variables o constantes. Para expresar estos literales tendremos en
cuenta que:
Los literales numéricos se expresarán por medio de notación decimal o de notación exponencial.
Ejemplos: 234, +341, 2e3, -2E-3, 7.45, 8.1e3.
Los literales tipo carácter y tipo cadena se deben delimitar con unas comillas simples.
Los literales lógicos son TRUE y FALSE.
El literal NULL que expresa que una variable no tiene ningún valor asignado.
280
Comentarios.
En los lenguajes de programación es muy conveniente utilizar comentarios en mitad del código. Los
comentarios no tienen ningún efecto sobre el código, pero sí ayudan mucho al programador o la
programadora a recordar qué se está intentando hacer en cada caso (más aún cuando el código es
compartido entre varias personas que se dedican a mejorarlo o corregirlo).
Los comentarios de una línea se expresarán por medio del delimitador --. Ejemplo:
a:=b; --asignación
Los comentarios de varias líneas se acotarán por medio de los delimitadores /* y */. Ejemplo:
Ejercicio resuelto
Dada la siguiente línea de código, haz su descomposición en las diferentes unidades léxicas que
contenga.
IF A <> B THEN iguales := FALSE; --No son iguales
La descomposición en unidades léxicas sería la siguiente:
Identificadores: A, B, iguales.
Identificadores (palabras reservadas): IF, THEN.
Delimitadores: <>, :=, ;.
Comentarios: --No son iguales.
En PL/SQL contamos con todos los tipos de datos simples utilizados en SQL y algunos más. En este
apartado vamos a enumerar los más utilizados.
Numéricos.
NUMBER: Tipo de dato numérico para almacenar números racionales. Podemos especificar su
escala (-84 ... 127) y su precisión (1 ... 38). La escala indica cuándo se redondea y hacia dónde.
Ejemplos. escala=2: 8.234 -> 8.23, escala=-3: 7689 -> 8000. PL/SQL también define algunos
subtipos como: DEC, DECIMAL, DOUBLE PRECISION, FLOAT, INTEGER, INT, NUMERIC, REAL, SMALLINT.
281
PLS_INTEGER: Tipo de datos numérico cuyo rango es el mismo que el del tipo de dato
BINARY_INTEGER, pero que su representación es distinta por lo que las operaciones aritméticas
llevadas a cabo con los mismos serán más eficientes que en los dos casos anteriores.
Alfanuméricos.
VARCHAR2: Tipo de dato para almacenar cadenas de longitud variable con un máximo de 32760.
Grandes objetos.
Otros.
BOOLEAN: TRUE/FALSE.
DATE: Tipo de dato para almacenar valores día/hora desde el 1 enero de 4712 a.c. hasta el 31
diciembre de 4712 d.c.
Hemos visto los tipos de datos simples más usuales. Los tipos de datos compuestos los dejaremos
para posteriores apartados.
Autoevaluación
En PL/SQL cuando vamos a trabajar con enteros es preferible utilizar el tipo de dato
BINARY_INTEGER, en vez de PLS_INTEGER.
Verdadero.
Falso.
Efectivamente, nuestros programas serán más eficientes al utilizar este tipo de dato debido a su representación
interna.
282
2.2.1.- Subtipos.
Cuántas veces no has deseado cambiarles el nombre a las cosas por alguno más común para ti.
Precisamente, esa es la posibilidad que nos ofrece PL/SQL con la utilización de los subtipos.
PL/SQL nos permite definir subtipos de tipos de datos para darles un nombre diferente y así aumentar
la legibilidad de nuestros programas. Los tipos de operaciones aplicables a estos subtipos serán las
mismas que los tipos de datos de los que proceden. La sintaxis será:
Donde subtipo será el nombre que le demos a nuestro subtipo y tipo_base será cualquier tipo de dato en
PL/SQL.
A la hora de especificar el tipo base, podemos utilizar el modificador %TYPE para indicar el tipo de dato
de una variable o de una columna de la base de datos y %ROWTYPE para especificar el tipo de un cursor
o tabla de una base de datos.
Los subtipos no podemos restringirlos, pero podemos usar un truco para conseguir el mismo efecto y
es por medio de una variable auxiliar:
Los subtipos son intercambiables con su tipo base. También son intercambiables si tienen el mismo
tipo base o si su tipo base pertenece a la misma familia:
DECLARE
SUBTYPE numero IS NUMBER;
numero_tres_digitos NUMBER(3);
mi_numero_de_la_suerte numero;
SUBTYPE encontrado IS BOOLEAN;
SUBTYPE resultado IS BOOLEAN;
lo_he_encontrado encontrado;
resultado_busqueda resultado;
SUBTYPE literal IS CHAR;
SUBTYPE sentencia IS VARCHAR2;
literal_nulo literal;
sentencia_vacia sentencia;
BEGIN
...
numero_tres_digitos := mi_numero_de_la_suerte; --legal
...
lo_he_encontrado := resultado_busqueda; --legal
...
sentencia_vacia := literal_nulo; --legal
...
END;
283
Autoevaluación
Indica la afirmación correcta.
Podemos definir un subtipo cuyo tipo base sea una tabla de la base de datos.
Podemos definir un subtipo de una variable pero no de una columna de la base de datos.
Efectivamente, veo que lo estás entendiendo.
Para declarar variables o constantes pondremos el nombre de la variable, seguido del tipo de datos y
opcionalmente una asignación. Si es una constante antepondremos la palabra CONSTANT al tipo de dato
(lo que querrá decir que no podemos cambiar su valor). Podremos sustituir el operador de asignación
en las declaraciones por la palabra reservada DEFAULT. También podremos forzar a que no sea nula
utilizando la palabra NOT NULL después del tipo y antes de la asignación. Si restringimos una variable
con NOT NULL deberemos asignarle un valor al declararla, de lo contrario PL/SQL lanzará la
excepción VALUE_ERROR (no te asustes que más adelante veremos lo que son las excepciones, pero
como adelanto te diré que es un error en tiempo de ejecución).
id SMALLINT;
hoy DATE := sysdate;
pi CONSTANT REAL:= 3.1415;
id SMALLINT NOT NULL; --ilegal, no está inicializada
id SMALLINT NOT NULL := 9999; --legal
El alcance y la visibilidad de las variables en PL/SQL será el típico de los lenguajes estructurados
basados en bloques, aunque eso lo veremos más adelante.
Conversión de tipos.
Aunque en PL/SQL existe la conversión implícita de tipos para tipos parecidos, siempre es aconsejable
utilizar la conversión explícita de tipos por medio de funciones de conversión
(TO_CHAR, TO_DATE, TO_NUMBER, …) y así evitar resultados inesperados.
Precedencia de operadores.
Al igual que en nuestro lenguaje matemático se utiliza una precedencia entre operadores a la hora de
realizar las operaciones aritméticas, en PL/SQL también se establece dicha precedencia para evitar
confusiones. Si dos operadores tienen la misma precedencia lo aconsejable es utilizar los paréntesis
(al igual que hacemos en nuestro lenguaje matemático) para alterar la precedencia de los mismos ya
284
que las operaciones encerradas entre paréntesis tienen mayor precedencia. En la tabla siguiente se
muestra la precedencia de los operadores de mayor a menor.
Precedencia de operadores.
Operador. Operación.
+, - Identidad, negación.
*, / Multiplicación, división.
OR Disyunción lógica.
Autoevaluación
Rellena el hueco con el resultado de las siguientes operaciones.
17
5+3*2**2 es igual a: .
10
2**3+6/3 es igual a: .
32
2**(3+6/3) es igual a: .
Su puntuación es 3/3.
285
La sintaxis es la siguiente:
[DECLARE
[Declaración de variables, constantes, cursores y excepciones]]
BEGIN
[Sentencias ejecutables]
[EXCEPTION
Manejadores de excepciones]
END;
Los bloques PL/SQL pueden anidarse a cualquier nivel. Como hemos comentado anteriormente el
ámbito y la visibilidad de las variables es la normal en un lenguaje procedimental. Por ejemplo, en el
siguiente fragmento de código se declara la variable aux en ambos bloques, pero en el bloque anidado
aux con valor igual a 10 actúa de variable global y aux con valor igual a 5 actúa como variable local, por
lo que en la comparación evaluaría a FALSE, ya que al tener el mismo nombre la visibilidad dominante
sería la de la variable local.
DECLARE
aux number := 10;
BEGIN
DECLARE
aux number := 5;
BEGIN
...
IF aux = 10 THEN --evalúa a FALSE, no entraría
...
END;
END;
Autoevaluación
En PL/SQL el bloque es la unidad básica, por lo que éstos no pueden anidarse.
Verdadero.
Falso.
Efectivamente, veo que lo vas entendiendo.
286
Control condicional.
Las estructuras de control condicional nos permiten llevar a cabo una acción u otra dependiendo de una
condición. Vemos sus diferentes variantes:
Sentencia IF-THEN
Sintaxis Ejemplo
IF condicion THEN IF (b<>0) THEN
secuencia_de_sentencias; c:=a/b;
END IF; END IF;
Sentencia IF-THEN-ELSE
Sintaxis Ejemplo
IF condicion THEN IF (b>0) THEN
Secuencia_de_sentencias1; c:=a*b;
ELSE ELSE
Secuencia_de_sentencias2; c:=a+b;
END IF; END IF;
IF-THEN-ELSIF: Con esta última forma de la sentencia condicional podemos hacer una selección
múltiple. Si la evaluación de la condición 1 da TRUE, ejecutamos la secuencia de sentencias 1,
sino evaluamos la condición 2. Si esta evalúa a TRUE ejecutamos la secuencia de sentencias 2 y
así para todos los ELSIF que haya. El último ELSE es opcional y es por si no se cumple ninguna de
las condiciones anteriores.
Sentencia IF-THEN-ELSIF
Sintaxis Ejemplo
IF (operacion = ‘SUMA’) THEN
resultado := arg1 + arg2;
IF condicion1 THEN
ELSIF (operacion = ‘RESTA’) THEN
Secuencia_de_sentencias1;
resultado := arg1 – arg2;
ELSIF condicion2 THEN
ELSIF (operacion = ‘PRODUCTO’) THEN
Secuencia_de_sentencias2;
resultado := arg1 * arg2;
...
ELSIF (arg2 <> 0) AND (operacion = ‘DIVISION’) THEN
[ELSE
resultado := arg1 / arg2;
Secuencia_de_sentencias;]
ELSE
END IF;
RAISE operacion_no_permitida;
END IF;
287
Autoevaluación
En PL/SQL no existen sentencias que nos permitan tomar una acción u otra dependiendo de una
condición.
Verdadero.
Falso.
Efectivamente, para eso existen las sentencias de control condicional.
Control iterativo.
Estas estructuras nos permiten ejecutar una secuencia de sentencias un determinado número de veces.
LOOP
secuencia_de_sentencias;
END LOOP;
EXIT:
Con esta sentencia forzamos a un bucle a terminar y pasa el control a la siguiente sentencia
después del bucle. Un EXIT no fuerza la salida de un bloque PL/SQL, sólo la salida del bucle:
LOOP
...
IF encontrado = TRUE THEN
EXIT;
END IF;
END LOOP;
EXIT WHEN condicion: Fuerza a salir del bucle cuando se cumple una determinada condición.
LOOP
...
EXIT WHEN encontrado;
END LOOP;
288
WHILE LOOP: Este tipo de bucle ejecuta la secuencia de sentencias mientras la condición sea
cierta:
Secuencia_de_sentencias; ...
FOR LOOP: Este bucle itera mientras el contador se encuentre en el rango definido:
Autoevaluación
Al utilizar REVERSE en un bucle FOR, en el rango debemos poner el número mayor el primero y
el menor el último.
Verdadero.
Falso.
Efectivamente, vas por buen camino.
289
2.5.- Excepciones.
Muchas veces te habrá pasado que surgen situaciones inesperadas con las que no contabas y a las
que tienes que hacer frente. Pues cuando programamos con PL/SQL pasa lo mismo, que a veces
tenemos que manejar errores debidos a situaciones diversas. Vamos a ver cómo tratarlos.
Las excepciones pueden estar definidas por el usuario o definidas internamente. Las excepciones
predefinidas se lanzarán automáticamente asociadas a un error de Oracle. Las excepciones definidas
por el usuario deberán definirse y lanzarse explícitamente.
En PL/SQL nosotros podemos definir nuestras propias excepciones en la parte DECLARE de cualquier
bloque. Estas excepciones podemos lanzarlas explícitamente por medio de la sentencia RAISE
nombre_excepción.
Sintaxis. Ejemplo.
DECLARE
categoria_erronea EXCEPTION;
BEGIN
DECLARE
...
nombre_excepcion EXCEPTION;
IF categoria<0 OR categoria>3 THEN
BEGIN
RAISE categoria_erronea;
...
END IF;
RAISE nombre_excepcion;
...
...
EXCEPTION
END;
WHEN categoria_erronea THEN
--manejamos la categoria errónea
END;
290
2.5.1.- Manejo de errores.
Ahora que ya sabemos lo que son las excepciones, cómo capturarlas y manejarlas y cómo definir y
lanzar las nuestras propias. Es la hora de comentar algunos detalles sobre el uso de las mismas.
El alcance de una excepción sigue las mismas reglas que el de una variable, por lo que si
nosotros redefinimos una excepción que ya es global para el bloque, la definición local
prevalecerá y no podremos capturar esa excepción a menos que el bloque en la que estaba
definida esa excepción fuese un bloque nombrado, y podremos capturarla usando la
sintaxis: nombre_bloque.nombre_excepcion.
Las excepciones predefinidas están definidas globalmente. No necesitamos (ni debemos)
redefinir las excepciones predefinidas:
DECLARE
no_data_found EXCEPTION;
BEGIN
SELECT * INTO ...
EXCEPTION
WHEN no_data_found THEN --captura la excepción local, no
--la global
END;
Cuando manejamos una excepción no podemos continuar por la siguiente sentencia a la que la
lanzó:
DECLARE
...
BEGIN
...
INSERT INTO familias VALUES
(id_fam, nom_fam, NULL, oficina);
INSERT INTO agentes VALUES
(id_ag, nom_ag, login, password, 0, 0, id_fam, NULL);
...
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
--manejamos la excepción debida a que el nombre de
--la familia ya existe, pero no podemos continuar por
--el INSERT INTO agentes, a no ser que lo pongamos
--explícitamente en el manejador
END;
Pero sí podemos encerrar la sentencia dentro de un bloque, y ahí capturar las posibles
excepciones, para continuar con las siguientes sentencias:
DECLARE
id_fam NUMBER;
nom_fam VARCHAR2(40);
oficina NUMBER;
id_ag NUMBER;
291
nom_ag VARCHAR2(60);
usuario VARCHAR2(20);
clave VARCHAR2(20);
BEGIN
...
BEGIN
INSERT INTO familias VALUES (id_fam, nom_fam, NULL, oficina);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
SELECT identificador INTO id_fam FROM familias WHERE nombre = nom_fam;
END;
INSERT INTO agentes VALUES (id_ag, nom_ag, login, password, 1, 1, id_fam, null);
...
END;
Ejercicio resuelto
Supongamos que queremos reintentar una transacción hasta que no nos dé ningún error. Para ello
deberemos encapsular la transacción en un bloque y capturar en éste las posibles excepciones. El
bloque lo metemos en un bucle y así se reintentará la transacción hasta que sea posible llevarla a cabo.
DECLARE
id_fam NUMBER;
nombre VARCHAR2(40);
oficina NUMBER;
BEGIN
...
LOOP
BEGIN
SAVEPOINT inicio;
INSERT INTO familias VALUES
(id_fam, nombre, NULL, oficina);
...
COMMIT;
EXIT;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK TO inicio;
id_fam := id_fam + 1;
END;
END LOOP;
...
END;
292
Citas para pensar
"Todo lo complejo puede dividirse en partes simples". René Descartes
3.1.- Registros.
El uso de los registros es algo muy común en los lenguajes de programación. PL/SQL también nos
ofrece este tipo de datos. En este apartado veremos qué son y cómo definirlos y utilizarlos.
Un registro es un grupo de elementos relacionados almacenados en campos, cada uno de los cuales
tiene su propio nombre y tipo de dato.
Por ejemplo, una dirección podría ser un registro con campos como calle, número, piso, puerta, código
postal, ciudad, provincia y país. Los registros hacen que la información sea más fácil de organizar y
representar. Para declarar un registro seguiremos la siguiente sintaxis:
donde:
El tipo del campo será cualquier tipo de dato válido en PL/SQL excepto REF CURSOR. La expresión será
cualquier expresión que evalúe al tipo de dato del campo:
...
mi_direccion.calle := ‘Ramirez Arellano’;
mi_direccion.numero := 15;
...
293
Para asignar un registro a otro, éstos deben ser del mismo tipo, no basta que tengan el mismo número
de campos y éstos emparejen uno a uno. Tampoco podemos comparar registros, aunque sean del
mismo tipo, ni tampoco comprobar si éstos son nulos. Podemos hacer SELECT en registros, pero no
podemos hacer INSERT desde registros:
DECLARE
TYPE familia IS RECORD
(
identificador NUMBER,
nombre VARCHAR2(40),
padre NUMBER,
oficina NUMBER
);
TYPE familia_aux IS RECORD
(
identificador NUMBER,
nombre VARCHAR2(40),
padre NUMBER,
oficina NUMBER
);
SUBTYPE familia_fila IS familias%ROWTYPE;
mi_fam familia;
mi_fam_aux familia_aux;
mi_fam_fila familia_fila;
BEGIN
...
mi_fam := mi_fam_aux; --ilegal
mi_fam := mi_fam_fila; --legal
IF mi_fam IS NULL THEN ... --ilegal
IF mi_fam = mi_fam_fila THEN ... --ilegal
SELECT * INTO mi_fam FROM familias ... --legal
INSERT INTO familias VALUES (mi_fam_fila); --ilegal
...
END;
Autoevaluación
Un registro se puede asignar a otro siempre que tenga el mismo número de campos y éstos
emparejen uno a uno.
Verdadero.
Falso.
Efectivamente, veo que lo vas entendiendo.
En PL/SQL las colecciones sólo pueden tener una dimensión. PL/SQL ofrece 2 clases de colecciones:
arrays de longitud variable y tablas anidadas.
294
Arrays de longitud variable.
Los elementos del tipo VARRAY son los llamados arrays de longitud variable. Son como los arrays de
cualquier otro lenguaje de programación, pero con la salvedad de que, a la hora de declararlos, nosotros
indicamos su tamaño máximo y el array podrá ir creciendo dinámicamente hasta alcanzar ese tamaño.
Un VARRAY siempre tiene un límite inferior igual a 1 y un límite superior igual al tamaño máximo.
Donde tamaño_máximo será un entero positivo y tipo_elementos será cualquier tipo de dato válido en PL/SQL,
excepto BINARY_INTEGER, BOOLEAN, LONG, LONG RAW, NATURAL, NATURALN, NCHAR, NCLOB, NVARCHAR2, objetos
que tengan como atributos TABLE o VARRAY, PLS_INTEGER, POSITIVE, POSITIVEN, SIGNTYPE, STRING, TABLE,
VARRAY. Si tipo_elementos es un registro, todos los campos deberían ser de un tipo escalar.
Cuando definimos un VARRAY, éste es automáticamente nulo, por lo que para empezar a utilizarlo
deberemos inicializarlo. Para ello podemos usar un constructor:
nombre_funcion(lista_parametros)(subindice).
IF familias_hijas1(i).identificador = 100 THEN ...
IF dame_familias_hijas(10)(i).identificador = 100 THEN ...
Un VARRAY puede ser asignado a otro si ambos son del mismo tipo.
DECLARE
TYPE tabla1 IS VARRAY(10) OF NUMBER;
TYPE tabla2 IS VARRAY(10) OF NUMBER;
mi_tabla1 tabla1 := tabla1();
mi_tabla2 tabla2 := tabla2();
mi_tabla tabla1 := tabla1();
BEGIN
...
mi_tabla := mi_tabla1; --legal
mi_tabla1 := mi_tabla2; --ilegal
...
END;
Para extender un VARRAY usaremos el método EXTEND. Sin parámetros, extendemos en 1 elemento nulo
el VARRAY. EXTEND(n) añade n elementos nulos al VARRAY y EXTEND(n,i) añade n copias del i-ésimo
elemento.
COUNT nos dirá el número de elementos del VARRAY. LIMIT nos dice el tamaño máximo del VARRAY. FIRST
siempre será 1. LAST siempre será igual a COUNT. PRIOR y NEXT devolverá el antecesor y el sucesor del
elemento.
295
Al trabajar con VARRAY podemos hacer que salte alguna de las siguientes excepciones, debidas a un
mal uso de los mismos: COLECTION_IS_NULL, SUBSCRIPT_BEYOND_COUNT, SUBSCRIPT_OUTSIDE_LIMIT y
VALUE_ERROR.
DECLARE
DECLARE
TYPE numeros IS VARRAY(20) OF
TYPE numeros IS VARRAY(20) OF
NUMBER;
INTEGER;
DECLARE tabla_numeros numeros := numeros();
v_numeros numeros := numeros( 10, 20, 30, 40
TYPE tab_num IS VARRAY(10) OF num NUMBER;
);
NUMBER; BEGIN
v_enteros numeros;
mi_tab tab_num; num := tabla_numeros.COUNT; --num
BEGIN
BEGIN := 0
v_enteros(1) := 15; --lanzaría
mi_tab := tab_num(); FOR i IN 1..10 LOOP
COLECTION_IS_NULL
FOR i IN 1..10 LOOP tabla_numeros.EXTEND;
v_numeros(5) := 20; --lanzaría
mi_tab.EXTEND; tabla_numeros(i) := i;
SUBSCRIPT_BEYOND_COUNT
mi_tab(i) := calcular_elemento(i); END LOOP;
v_numeros(-1) := 5; --lanzaría
END LOOP; num := tabla_numeros.COUNT; --num := 10
SUBSCRIPT_OUTSIDE_LIMIT
... num := tabla_numeros.LIMIT; --num := 20
v_numeros(‘A’) := 25; --lanzaría
END; num := tabla_numeros.FIRST; --num := 1;
VALUE_ERROR
num := tabla_numeros.LAST; --num := 10;
....
...
END;
END;
Autoevaluación
Indica, de entre las siguientes, cuál es la afirmación correcta referida a VARRAY.
Donde tipo_elementos tendrá las mismas restricciones que para los VARRAY.
Al igual que pasaba con los VARRAY, al declarar una tabla anidada, ésta es automáticamente nula, por
lo que deberemos inicializarla antes de usarla.
Para referenciar elementos usamos la misma sintaxis que para los VARRAY.
296
Para extender una tabla usamos EXTEND exactamente igual que para los VARRAY. COUNT nos dirá el
número de elementos, que no tiene por qué coincidir con LAST. LIMIT no tiene sentido y devuelve NULL.
EXISTS(n) devuelve TRUE si el elemento existe, y FALSE en otro caso (el elemento ha sido borrado). FIRST
devuelve el primer elemento que no siempre será 1, ya que hemos podido borrar elementos del
principio. LAST devuelve el último elemento. PRIOR y NEXT nos dicen el antecesor y sucesor del elemento
(ignorando elementos borrados). TRIM sin argumentos borra un elemento del final de la tabla. TRIM(n)
borra n elementos del final de la tabla. TRIM opera en el tamaño interno, por lo que si encuentra un
elemento borrado con DELETE, lo incluye para ser eliminado de la colección. DELETE(n) borra el n-ésimo
elemento. DELETE(n, m) borra del elemento n al m. Si después de hacer DELETE, consultamos si el
elemento existe nos devolverá FALSE.
Al trabajar con tablas anidadas podemos hacer que salte alguna de las siguientes excepciones, debidas
a un mal uso de las mismas: COLECTION_IS_NULL, NO_DATA_FOUND, SUBSCRIPT_BEYOND_COUNT y
VALUE_ERROR.
DECLARE
TYPE numeros IS TABLE OF NUMBER;
tabla_numeros numeros := numeros();
num NUMBER;
BEGIN
DECLARE
num := tabla_numeros.COUNT; --num := 0
TYPE numeros IS TABLE OF NUMBER;
FOR i IN 1..10 LOOP
tabla_num numeros := numeros();
tabla_numeros.EXTEND;
tabla1 numeros;
tabla_numeros(i) := i;
BEGIN
END LOOP;
tabla1(5) := 0; --lanzaría COLECTION_IS_NULL
num := tabla_numeros.COUNT; --num := 10
tabla_num.EXTEND(5);
tabla_numeros.DELETE(10);
tabla_num.DELETE(4);
num := tabla_numeros.LAST; --num := 9
tabla_num(4) := 3; --lanzaría NO_DATA_FOUND
num := tabla_numeros.FIRST; --num := 1
tabla_num(6) := 10; --lanzaría SUBSCRIPT_BEYOND_COUNT
tabla_numeros.DELETE(1);
tabla_num(-1) := 0; --lanzaría SUBSCRIPT_OUTSIDE_LIMIT
num := tabla_numeros.FIRST; --num := 2
tabla_num(‘y’) := 5;--lanzaría VALUE_ERROR
FOR i IN 1..4 LOOP
END;
tabla_numeros.DELETE(2*i);
END LOOP;
num := tabla_numeros.COUNT; --num := 4
num := tabla_numeros.LAST; --num := 9
...
END;
Autoevaluación
Las tablas anidadas podemos hacer que crezcan dinámicamente, pero no podemos borrar
elementos.
Verdadero.
Falso.
¡Muy bien!
297
3.3.- Cursores.
En los apartados anteriores hemos visto algunos tipos de datos compuestos cuyo uso es común en
otros lenguajes de programación. Sin embargo, en este apartado vamos a ver un tipo de dato, que
aunque se puede asemejar a otros que ya conozcas, su uso es exclusivo en la programación de las
bases de datos y que es el cursor.
Un cursor no es más que una estructura que almacena el conjunto de filas devuelto por una consulta a
la base de datos.
Oracle usa áreas de trabajo para ejecutar sentencias SQL y almacenar la información procesada. Hay
2 clases de cursores: implícitos y explícitos. PL/SQL declara implícitamente un cursor para todas las
sentencias SQL de manipulación de datos, incluyendo consultas que devuelven una sola fila. Para las
consultas que devuelven más de una fila, se debe declarar explícitamente un cursor para procesar las
filas individualmente.
En este primer apartado vamos a hablar de los cursores implícitos y de los atributos de un cursor (estos
atributos tienen sentido con los cursores explícitos, pero los introducimos aquí para ir abriendo boca),
para luego pasar a ver los cursores explícitos y terminaremos hablando de los cursores variables.
Cursores implícitos.
Oracle abre implícitamente un cursor para procesar cada sentencia SQL que no esté asociada con un
cursor declarado explícitamente.
Con un cursor implícito no podemos usar las sentencias OPEN, FETCH y CLOSE para controlar el cursor.
Pero sí podemos usar los atributos del cursor para obtener información sobre las sentencias SQL más
recientemente ejecutadas.
Atributos de un cursor.
Cada cursor tiene 4 atributos que podemos usar para obtener información sobre la ejecución del mismo
o sobre los datos. Estos atributos pueden ser usados en PL/SQL, pero no en SQL. Aunque estos
atributos se refieren en general a cursores explícitos y tienen que ver con las operaciones que hayamos
realizado con el cursor, es deseable comentarlas aquí y en el siguiente apartado tomarán pleno sentido.
%FOUND: Después de que el cursor esté abierto y antes del primer FETCH, %FOUND devuelve NULL.
Después del primer FETCH, %FOUND devolverá TRUE si el último FETCH ha devuelto una fila y FALSE en
caso contrario. Para cursores implícitos %FOUND devuelve TRUE si un INSERT, UPDATE o DELETE afectan
a una o más de una fila, o un SELECT ... INTO ... devuelve una o más filas. En otro
caso %FOUND devuelve FALSE.
%NOTFOUND: Es lógicamente lo contrario a %FOUND.
%ISOPEN: Evalúa a TRUE si el cursor está abierto y FALSE en caso contrario. Para cursores implícitos,
como Oracle los cierra automáticamente, %ISOPEN evalúa siempre a FALSE.
%ROWCOUNT: Para un cursor abierto y antes del primer FETCH, %ROWCOUNT evalúa a 0. Después de
cada FETCH, %ROWCOUNT es incrementado y evalúa al número de filas que hemos procesado. Para
cursores implícitos %ROWCOUNT evalúa al número de filas afectadas por un INSERT, UPDATE o DELETE o
el número de filas devueltas por un SELECT ... INTO ...
298
Debes conocer
Aunque todavía no hemos visto las operaciones que se pueden realizar con un cursor explícito, es
conveniente que te vayas familiarizando con el Anexo I, en el que se explica la evaluación de sus
atributos según las operaciones que hayamos realizado con el cursor y que tomarán pleno sentido
cuando veamos el siguiente apartado.
Donde tipo_devuelto debe representar un registro o una fila de una tabla de la base de datos, y parámetro
sigue la siguiente sintaxis:
Ejemplos:
Además, como hemos visto en la declaración, un cursor puede tomar parámetros, los cuales pueden
aparecer en la consulta asociada como si fuesen constantes. Los parámetros serán de entrada, un
cursor no puede devolver valores en los parámetros actuales. A un parámetro de un cursor no podemos
imponerle la restricción NOT NULL.
CURSOR c1 (cat INTEGER DEFAULT 0) IS SELECT * FROM agentes WHERE categoria = cat;
Cuando abrimos un cursor, lo que se hace es ejecutar la consulta asociada e identificar el conjunto
resultado, que serán todas las filas que emparejen con el criterio de búsqueda de la consulta. Para abrir
un cursor usamos la sintaxis:
Ejemplos:
OPEN cAgentes;
OPEN c1(1);
OPEN c1;
La sentencia FETCH devuelve una fila del conjunto resultado. Después de cada FETCH, el cursor avanza
a la próxima fila en el conjunto resultado.
299
Para cada valor de columna devuelto por la consulta asociada al cursor, debe haber una variable que
se corresponda en la lista de variables después del INTO.
BEGIN
...
OPEN cFamilias;
LOOP
FETCH cFamilias INTO mi_id, mi_nom, mi_fam, mi_ofi;
EXIT WHEN cFamilias%NOTFOUND;
...
END LOOP;
...
END;
Una vez procesado el cursor, deberemos cerrarlo, con lo que deshabilitamos el cursor y el conjunto
resultado queda indefinido.
CLOSE cFamilias;
Una vez cerrado el cursor podemos reabrirlo, pero cualquier otra operación que hagamos con el cursor
cerrado lanzará la excepción INVALID_CURSOR.
También podemos simplificar la operación de procesamiento de un cursor, por medio de los bucles para
cursores, los cuales declaran implícitamente una variable índice definida como %ROWTYPE para el
cursor, abren el cursor, se van trayendo los valores de cada fila del cursor, almacenándolas en la
variable índice, y finalmente cierran el cursor.
BEGIN
...
FOR cFamilias_rec IN cFamilias LOOP
--Procesamos las filas accediendo a
--cFamilias_rec.identificador, cFamilias_rec.nombre,
--cFamilias_rec.familia, ...
END LOOP;
...
END;
Autoevaluación
En PL/SQL los cursores son abiertos al definirlos.
Verdadero.
Falso.
Efectivamente, debemos abrirlos por medio de la sentencia OPEN.
300
3.3.2.- Cursores variables.
Oracle, además de los cursores vistos anteriormente, nos permite definir cursores variables que son
como punteros a cursores, por lo que podemos usarlos para referirnos a cualquier tipo de consulta. Los
cursores serían estáticos y los cursores variables serían dinámicos.
Definir un tipo REF CURSOR y entonces declarar una variable de ese tipo.
Una vez definido el cursor variable debemos asociarlo a una consulta (notar que esto no se hace
en la parte declarativa, sino dinámicamente en la parte de ejecución) y esto lo hacemos con la
sentencia OPEN-FOR utilizando la siguiente sintaxis:
Un cursor variable no puede tomar parámetros. Podemos usar los atributos de los cursores para
cursores variables.
Además, podemos usar varios OPEN-FOR para abrir el mismo cursor variable para diferentes consultas.
No necesitamos cerrarlo antes de reabrirlo. Cuando abrimos un cursor variable para una consulta
diferente, la consulta previa se pierde.
Una vez abierto el cursor variable, su manejo es idéntico a un cursor. Usaremos FETCH para traernos
las filas, usaremos sus atributos para hacer comprobaciones y lo cerraremos cuando dejemos de usarlo.
DECLARE
TYPE cursor_Agentes IS REF CURSOR RETURN agentes%ROWTYPE;
cAgentes cursor_Agentes;
agente cAgentes%ROWTYPE;
BEGIN
...
OPEN cAgentes FOR SELECT * FROM agentes WHERE oficina = 1;
LOOP
FETCH cAgentes INTO agente;
EXIT WHEN cAgentes%NOTFOUND;
...
END LOOP;
CLOSE cAgentes;
...
END;
Autoevaluación
A los cursores variables no podemos pasarles parámetros al abrirlos.
Verdadero.
Falso.
Correcto, veo que lo vas entendiendo.
301
Los cursores variables se abren exactamente igual que los cursores explícitos.
Verdadero.
Falso.
Correcto, ya que debemos abrirlo por medio de la sentencia OPEN-FOR con la que le asociamos la consulta.
PL/SQL nos permite definir funciones y procedimientos. Además, nos permite agrupar todas aquellas
que tengan relación en paquetes. También permite la utilización de objetos. Todo esto es lo que
veremos en este apartado y conseguiremos darles modularidad a nuestras aplicaciones, aumentar la
reusabilidad y mantenimiento del código y añadir grados de abstracción a los problemas.
4.1.- Subprogramas.
Los subprogramas son bloques nombrados a los cuales les podemos pasar parámetros y los podemos
invocar. Además, los subprogramas pueden estar almacenados en la base de datos o estar encerrados
en otros bloques. Si el programa está almacenado en la base de datos, podremos invocarlo si tenemos
permisos suficientes y si está encerrado en otro bloque lo podremos invocar si tenemos visibilidad sobre
el mismo.
Hay dos clases de subprogramas: las funciones y los procedimientos. Las funciones devuelven un valor
y los procedimientos no.
Donde:
302
Algunas consideraciones que debes tener en cuenta son las siguientes:
Podemos definir subprogramas al final de la parte declarativa de cualquier bloque. En Oracle, cualquier
identificador debe estar declarado antes de usarse, y eso mismo pasa con los subprogramas, por lo
que deberemos declararlos antes de usarlos.
DECLARE
hijos NUMBER;
FUNCTION hijos_familia( id_familia NUMBER )
RETURN NUMBER IS
hijos NUMBER;
BEGIN
SELECT COUNT(*) INTO hijos FROM agentes
WHERE familia = id_familia;
RETURN hijos;
END hijos_familia;
BEGIN
...
END;
DECLARE
PROCEDURE calculo(...); --declaración hacia delante
--Definimos subprogramas agrupados lógicamente
PROCEDURE inicio(...) IS
BEGIN
...
calculo(...);
...
END;
...
BEGIN
...
END;
Autoevaluación
Una función siempre debe devolver un valor.
Verdadero.
Falso.
Efectivamente, una función obligatoriamente debe devolver un valor.
303
En PL/SQL no podemos definir subprogramas mutuamente recursivos.
Verdadero.
Falso.
¡Muy bien!
Cuando los subprogramas son almacenados en la base de datos, para ellos no podemos utilizar las
declaraciones hacia delante, por lo que cualquier subprograma almacenado en la base de datos deberá
conocer todos los subprogramas que utilice.
BEGIN
...
hijos := hijos_familia(10);
...
END;
Cuando almacenamos un subprograma en la base de datos éste es compilado antes. Si hay algún error
se nos informará de los mismos y deberemos corregirlos por medio de la cláusula OR REPLACE, antes de
que el subprograma pueda ser utilizado.
304
Hay varias vistas del diccionario de datos que nos ayudan a llevar un control de los subprogramas, tanto
para ver su código, como los errores de compilación. También hay algunos comandos de SQL*Plus que
nos ayudan a hacer lo mismo, pero de forma algo menos engorrosa.
También existe la vista USER_OBJECTS de la cual podemos obtener los nombres de todos los
subprogramas almacenados.
Autoevaluación
Una vez que hemos almacenado un subprograma en la base de datos podemos consultar su
código mediante la vista USER_OBJECTS.
Verdadero.
Falso.
Sí, lo estás captando a la primera.
Cuando llamamos a un subprograma, los parámetros actuales podemos escribirlos utilizando notación
posicional o notación nombrada, es decir, la asociación entre parámetros actuales y formales podemos
hacerla por posición o por nombre.
En la notación posicional, el primer parámetro actual se asocia con el primer parámetro formal, el
segundo con el segundo, y así para el resto. En la notación nombrada usamos el operador => para
asociar el parámetro actual al parámetro formal. También podemos usar notación mixta.
Los parámetros pueden ser de entrada al subprograma, de salida, o de entrada/salida. Por defecto si
a un parámetro no le especificamos el modo, éste será de entrada. Si el parámetro es de salida o de
entrada/salida, el parámetro actual debe ser una variable.
Un parámetro de entrada permite que pasemos valores al subprograma y no puede ser modificado en
el cuerpo del subprograma. El parámetro actual pasado a un subprograma como parámetro formal de
entrada puede ser una constante o una variable.
305
Un parámetro de salida permite devolver valores y dentro del subprograma actúa como variable no
inicializada. El parámetro formal debe ser siempre una variable.
Un parámetro de entrada-salida se utiliza para pasar valores al subprograma y/o para recibirlos, por lo
que un parámetro formal que actúe como parámetro actual de entrada-salida siempre deberá ser una
variable.
Los parámetros de entrada los podemos inicializar a un valor por defecto. Si un subprograma tiene un
parámetro inicializado con un valor por defecto, podemos invocarlo prescindiendo del parámetro y
aceptando el valor por defecto o pasando el parámetro y sobreescribiendo el valor por defecto. Si
queremos prescindir de un parámetro colocado entre medias de otros, deberemos usar notación
nombrada o si los parámetros restantes también tienen valor por defecto, omitirlos todos.
Autoevaluación
Indica de entre las siguientes afirmaciones las que creas que son correctas.
No existen los parámetros de salida ya que para eso existen las funciones.
Correcto
306
Autoevaluación
En PL/SQL no podemos sobrecargar subprogramas que aceptan el mismo número y tipo de
parámetros, pero sólo difieren en el modo.
Verdadero.
Falso.
Correcto, veo que lo has entendido.
Verdadero.
Falso.
¡Muy bien!
4.2.- Paquetes.
Un paquete es un objeto que agrupa tipos, elementos y subprogramas. Suelen tener dos partes: la
especificación y el cuerpo, aunque algunas veces el cuerpo no es necesario.
En la parte de especificación declararemos la interfaz del paquete con nuestra aplicación y en el cuerpo
es donde implementaremos esa interfaz.
La parte de inicialización sólo se ejecuta una vez, la primera vez que el paquete es referenciado.
Para referenciar las partes visibles de un paquete, lo haremos por medio de la notación del punto.
BEGIN
...
call_center.borra_agente( 10 );
...
END;
307
4.2.1.- Ejemplos de utilización del paquete
DBMS_OUTPUT.
Oracle nos suministra un paquete público con el cual podemos enviar mensajes desde subprogramas
almacenados, paquetes y disparadores, colocarlos en un buffer y leerlos desde otros subprogramas
almacenados, paquetes o disparadores.
SQL*Plus permite visualizar los mensajes que hay en el buffer, por medio del comando SET
SERVEROUTPUT ON. La utilización fundamental de este paquete es para la depuración de nuestros
subprogramas.
Veamos uno a uno los subprogramas que nos suministra este paquete:
Habilita las llamadas a los demás subprogramas. No es necesario cuando está activada la
opción SERVEROUTPUT. Podemos pasarle un parámetro indicando el tamaño del buffer.
ENABLE
ENABLE( buffer_size IN INTEGER DEFAULT 2000);
Deshabilita las llamadas a los demás subprogramas y purga el buffer. Como con ENABLE no es necesario
si estamos usando la opción SERVEROUTPUT.
DISABLE
DISABLE();
Coloca un salto de línea en el buffer. Utilizado cuando componemos una línea usando varios PUT.
NEW_LINE
NEW_LINE();
Lee una línea del buffer colocándola en el parámetro line y obviando el salto de línea. El
parámetro status devolverá 0 si nos hemos traído alguna línea y 1 en caso contrario.
GET_LINE
GET_LINE(line OUT VARCHAR2, status OUT VARCHAR2);
Intenta leer el número de líneas indicado en numlines. Una vez ejecutado, numlines contendrá el número de
líneas que se ha traído. Las líneas traídas las coloca en el parámetro lines del tipo CHARARR, tipo definido
el paquete DBMS_OUTPUT como una tabla de VARCHAR2(255).
GET_LINES
GET_LINES(lines OUT CHARARR, numlines IN OUT INTEGER);
308
Ejercicio resuelto
Debes crear un procedimiento que visualice todos los agentes, su nombre, nombre de la familia y/o nombre de
la oficina a la que pertenece.
Recuerda que para ejecutarlo desde SQL*Plus debes ejecutar las siguientes sentencias:
309
4.3.- Objetos.
Hoy día, la programación orientada a objetos es uno de los paradigmas más utilizados y casi todos los
lenguajes de programación la soportan. En este apartado vamos a dar unas pequeñas pinceladas de
su uso en PL/SQL que serán ampliados en la siguiente unidad de trabajo.
Un tipo de objeto es un tipo de dato compuesto, que encapsula unos datos y las funciones y
procedimientos necesarios para manipular esos datos. Las variables son los atributos y los
subprogramas son llamados métodos. Podemos pensar en un tipo de objeto como en una entidad que
posee unos atributos y un comportamiento (que viene dado por los métodos).
Cuando creamos un tipo de objeto, lo que estamos creando es una entidad abstracta que
especifica los atributos que tendrán los objetos de ese tipo y define su comportamiento.
Cuando instanciamos un objeto estamos particularizando la entidad abstracta a una en particular,
con los atributos que tiene el tipo de objeto, pero con un valor dado y con el mismo
comportamiento.
Los tipos de objetos tiene 2 partes: una especificación y un cuerpo. La parte de especificación declara
los atributos y los métodos que harán de interfaz de nuestro tipo de objeto. En el cuerpo se implementa
la parte de especificación. En la parte de especificación debemos declarar primero los atributos y
después los métodos. Todos los atributos son públicos (visibles). No podemos declarar atributos en el
cuerpo, pero sí podemos declarar subprogramas locales que serán visibles en el cuerpo del objeto y
que nos ayudarán a implementar nuestros métodos.
Los métodos dentro de un tipo de objeto pueden sobrecargarse. No podemos sobrecargarlos si los
parámetros formales sólo difieren en el modo o pertenecen a la misma familia. Tampoco podremos
sobrecargar una función miembro si sólo difiere en el tipo devuelto.
Una vez que tenemos creado el objeto, podemos usarlo en cualquier declaración. Un objeto cuando se
declara sigue las mismas reglas de alcance y visibilidad que cualquier otra variable.
Cuando un objeto se declara éste es automáticamente NULL. Dejará de ser nulo cuando lo inicialicemos
por medio de su constructor o cuando le asignemos otro. Si intentamos acceder a los atributos de un
objeto NULL saltará la excepción ACCES_INTO_NULL.
Todos los objetos tienen constructores por defecto con el mismo nombre que el tipo de objeto y acepta
tantos parámetros como atributos del tipo de objeto y con el mismo tipo. PL/SQL no llama implícitamente
a los constructores, deberemos hacerlo nosotros explícitamente.
DECLARE
familia1 Familia;
BEGIN
...
familia1 := Familia( 10, ‘Fam10’, 1, NULL );
...
END;
Un tipo de objeto puede tener a otro tipo de objeto entre sus atributos. El tipo de objeto que hace de
atributo debe ser conocido por Oracle. Si 2 tipos de objetos son mutuamente dependientes, podemos
usar una declaración hacia delante para evitar errores de compilación.
Ejercicio resuelto
¿Cómo declararías los objetos para nuestra base de datos de ejemplo?
CREATE OBJECT Oficina; --Definición hacia delante
311
CREATE OBJECT Oficina AS OBJECT (
identificador NUMBER,
nombre VARCHAR2(20),
jefe Agente,
...
);
Una función miembro mapa es una función sin parámetros que devuelve un tipo de dato: DATE, NUMBER
o VARCHAR2 y sería similar a una función hash. Se definen anteponiendo la palabra clave MAP y sólo
puede haber una para el tipo de objeto.
Una función miembro de orden es una función que acepta un parámetro del mismo tipo del tipo de
objeto y que devuelve un número negativo si el objeto pasado es menor, cero si son iguales y un número
positivo si el objeto pasado es mayor.
312
CREATE TYPE BODY Oficina AS
ORDER MEMBER FUNCTION igual ( ofi Oficina ) RETURN INTEGER IS
BEGIN
IF (identificador < ofi.identificador) THEN
RETURN –1;
ELSIF (identificador = ofi.identificador) THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
END;
...
END;
Autoevaluación
Los métodos de un objeto sólo pueden ser procedimientos.
Verdadero.
Falso.
Efectivamente, ya que las funciones también pueden ser métodos.
Al crearlo.
5.- Disparadores.
En este apartado vamos a tratar una herramienta muy potente proporcionada por PL/SQL para
programar nuestra base de datos que son los disparadores o triggers en inglés.
Un disparador no es más que un procedimiento que es ejecutado cuando se realiza alguna sentencia
de manipulación de datos sobre una tabla dada y bajo unas circunstancias establecidas a la hora de
definirlo.
Llevar a cabo auditorías sobre la historia de los datos en nuestra base de datos.
Garantizar complejas reglas de integridad.
Automatizar la generación de valores derivados de columnas.
Etc.
313
Cuando diseñamos un disparador debemos tener en cuenta que:
Un disparador puede ser lanzado antes o después de realizar la operación que lo lanza. Por lo que
tendremos disparadores BEFORE y disparadores AFTER.
Un disparador puede ser lanzado una vez por sentencia o una vez por cada fila a la que afecta. Por lo
que tendremos disparadores de sentencia y disparadores de fila.
Un disparador puede ser lanzado al insertar, al actualizar o al borrar de una tabla, por lo que tendremos
disparadores INSERT, UPDATE o DELETE (o mezclados).
Autoevaluación
En PL/SQL sólo podemos definir disparadores de fila.
Verdadero.
Falso.
Efectivamente, ya que también podemos definir disparador de sentencia.
La diferente entre un disparador de fila y uno de sentencia es que el de fila es lanzado una vez
por fila a la que afecta la sentencia y el de sentencia es lanzado una sola vez.
Verdadero.
Falso.
Muy bien, vas por buen camino.
314
Donde nombre nos indica el nombre que le damos al disparador, momento nos dice cuándo será lanzado el
disparador (BEFORE o AFTER), acontecimiento será la acción que provoca el lanzamiento del disparador
(INSERT y/o DELETE y/o UPDATE). REFERENCING y WHEN sólo podrán ser utilizados con disparadores para
filas. REFERENCING nos permite asignar un alias a los valores NEW o/y OLD de las filas afectadas por la
operación, y WHEN nos permite indicar al disparador que sólo sea lanzado cuando sea TRUE una cierta
condición evaluada para cada fila afectada.
En un disparador de fila, podemos acceder a los valores antiguos y nuevos de la fila afectada por la
operación, referenciados como :old y :new (de ahí que podamos utilizar la opción REFERENCING para
asignar un alias). Si el disparador es lanzado al insertar, el valor antiguo no tendrá sentido y el valor
nuevo será la fila que estamos insertando. Para un disparador lanzado al actualizar el valor antiguo
contendrá la fila antes de actualizar y el valor nuevo contendrá la fila que vamos actualizar. Para un
disparador lanzado al borrar sólo tendrá sentido el valor antiguo.
En el cuerpo de un disparador también podemos acceder a unos predicados que nos dicen qué tipo de
operación se está llevando a cabo, que son: INSERTING, UPDATING y DELETING.
Un disparador de fila no puede acceder a la tabla asociada. Se dice que esa tabla está mutando. Si un
disparador es lanzado en cascada por otro disparador, éste no podrá acceder a ninguna de las tablas
asociadas, y así recursivamente.
Si tenemos varios tipos de disparadores sobre una misma tabla, el orden de ejecución será:
Existe una vista del diccionario de datos con información sobre los disparadores: USER_TRIGGERS;
SQL>DESC USER_TRIGGERS;
Name Null? Type
------------------------------- -------- ----
TRIGGER_NAME NOT NULL VARCHAR2(30)
TRIGGER_TYPE VARCHAR2(16)
TRIGGERING_EVENT VARCHAR2(26)
TABLE_OWNER NOT NULL VARCHAR2(30)
TABLE_NAME NOT NULL VARCHAR2(30)
REFERENCING_NAMES VARCHAR2(87)
WHEN_CLAUSE VARCHAR2(4000)
STATUS VARCHAR2(8)
DESCRIPTION VARCHAR2(4000)
TRIGGER_BODY LONG
315
5.2.- Ejemplos de disparadores.
Ya hemos visto qué son los disparadores, los tipos que existen, cómo definirlos y algunas
consideraciones a tener en cuenta a la hora de trabajar con ellos.
Ahora vamos a ver algunos ejemplos de su utilización con los que podremos comprobar la potencia que
éstos nos ofrecen.
Ejercicio resuelto
Como un agente debe pertenecer a una familia o una oficina, pero no puede pertenecer a una
familia y a una oficina a la vez, deberemos implementar un disparador para llevar a cabo esta
restricción que Oracle no nos permite definir.
Para este cometido definiremos un disparador de fila que saltará antes de que insertemos o
actualicemos una fila en la tabla agentes, cuyo código podría ser el siguiente:
Ejercicio resuelto
Supongamos que tenemos una tabla de históricos para agentes que nos permita auditar las
familias y oficinas por la que ha ido pasando un agente. La tabla tiene la fecha de inicio y la fecha
de finalización del agente en esa familia u oficina, el identificador del agente, el nombre del
agente, el nombre de la familia y el nombre de la oficina. Queremos hacer un disparador que
inserte en esa tabla.
Para llevar a cabo esta tarea definiremos un disparador de fila que saltará después de insertar,
actualizar o borrar una fila en la tabla agentes, cuyo código podría ser el siguiente:
Ejercicio resuelto
Queremos realizar un disparador que no nos permita llevar a cabo operaciones con familias si
no estamos en la jornada laboral.
317
6.- Interfaces de programación de aplicaciones
para lenguajes externos.
Llegados a este punto ya sabemos programar nuestra base de datos, pero al igual que
a Juan y María te surgirá la duda de si todo lo que has hecho puedes aprovecharlo desde cualquier
otro lenguaje de programación. La respuesta es la misma que dio Ada: SÍ.
En este apartado te vamos a dar algunas referencias sobre las APIs para acceder a las bases de datos
desde un lenguaje de programación externo. No vamos a ser exhaustivos ya que eso queda fuera del
alcance de esta unidad e incluso de este módulo. Todo ello lo vas a estudiar con mucha más
profundidad en otros módulos de este ciclo (o incluso ya lo conoces). Aquí sólo pretendemos que sepas
que existen y que las puedes utilizar.
Las primeras APIs utilizadas para acceder a bases de datos Oracle fueron las que el
mismo Oracle proporcionaba: Pro*C, Pro*Fortran y Pro*Cobol. Todas permitían embeber llamadas a la
base de datos en nuestro programa y a la hora de compilarlo, primero debíamos pasar el precompilador
adecuado que trasladaba esas llamadas embebidas en llamadas a una librería utilizada en tiempo de
ejecución. Sin duda, el más utilizado fue Pro*C, ya que el lenguaje C y C++ tuvieron un gran auge.
Hoy día existen muchas más APIs para acceder a las bases de datos ya que tanto los lenguajes de
programación como las tecnologías han evolucionado mucho. Antes la programación para la web casi
no existía y por eso todos los programas que accedían a bases de datos lo hacían bien en local o bien
a través de una red local. Hoy día eso sería impensable, de ahí que las APIs también hayan
evolucionado mucho y tengamos una gran oferta a nuestro alcance para elegir la que más se adecue a
nuestras necesidades.
De los agentes queremos conocer su nombre, su clave y contraseña para entrar al sistema, su categoría
y su habilidad que será un número entre 0 y 9 indicando su habilidad para atender llamadas.
Para las familias sólo nos interesa conocer su nombre.
Finalmente, para las oficinas queremos saber su nombre, domicilio, localidad y código postal de la misma.
318
Un posible modelo entidad-relación para el problema expuesto podría ser el siguiente:
De este modelo de datos surgen tres tablas, que podrían ser creadas en Oracle con las siguientes
sentencias:
Vamos a insertar algunas filas para que las pruebas de nuestros ejemplos tengan algo de sentido. Para
ello podemos utilizar las siguientes sentencias:
insert into oficinas values (1, 'Madrid', 'Gran vía, 37', 'Madrid', 28000);
insert into oficinas values (2, 'Granada', 'Camino Ronda, 50', 'Granada', 36000);
insert into oficinas values (3, 'Jaén', 'Gran Eje, 80', 'Jaén', 27000);
insert into agentes values (31, 'José Ramón Jiménez Reyes', 'jrjr', 'sup31', 9, 2, NULL, 3);
insert into agentes values (311, 'Pedro Fernández Arias', 'pfa', 'ag311', 5, 0, 31, NULL);
insert into agentes values (312, 'Vanesa Sánchez Rojo', 'vsr', 'ag312', 5, 0, 31, NULL);
insert into agentes values (313, 'Francisco Javier García Escobedo', 'fjge', 'ag313', 5, 0, 31, NULL);
insert into agentes values (314, 'Pilar Ramirez Pérez', 'prp', 'ag314', 5, 0, 31, NULL);
insert into agentes values (315, 'José Luis García Martínez', 'jlgm', 'ag315', 5, 0, 31, NULL);
insert into agentes values (21, 'Sebastián López Ojeda', 'slo', 'sup21', 9, 2, NULL, 2);
insert into agentes values (211, 'Diosdado Sánchez Hernández', 'dsh', 'ag211', 8, 1, 21, NULL);
insert into agentes values (2111, 'José Juan Cano Pardo', 'jjcp', 'ag2111', 5, 0, 211, NULL);
insert into agentes values (2112, 'Flor Moncada Añón', 'ag2112', 'fma', 5, 0, 211, NULL);
insert into agentes values (2113, 'Juan Manuel Alcazar Donaire', 'jmad', 'ag2113', 5, 0, 211, NULL);
insert into agentes values (2121, 'Manuel Jesús Rubia Mateos', 'mjrm', 'ag2121', 5, 0, 212, NULL);
insert into agentes values (2122, 'Esther López Delgado', 'eld', 'ag2122', 5, 0, 212, NULL);
insert into agentes values (2123, 'Francisco Javier Cabrerizo Membrilla', 'fjcm', 'ag2123', 5, 0, 212, NULL);
insert into agentes values (2124, 'Verónica Cabrerizo Menbrilla', 'vcm', 'ag2124', 5, 0, 212, NULL);
insert into agentes values (2125, 'María José Navascués Morales', 'mjnm', 'ag2125', 5, 0, 212, NULL);
insert into agentes values (2131, 'Isabel Cruz Granados', 'icg', 'ag2131', 5, 0, 213, NULL);
insert into agentes values (2132, 'Antonio Casado Fernández', 'acf', 'ag2132', 5, 0, 213, NULL);
insert into agentes values (2133, 'Gabriel Callejón García', 'gcg', 'ag2133', 5, 0, 213, NULL);
insert into agentes values (2134, 'Enrique Cano Balsera', 'ecb', 'ag2134', 5, 0, 213, NULL);
insert into agentes values (11, 'Narciso Jáimez Toro', 'njt', 'sup11', 9, 2, NULL, 1);
insert into agentes values (111, 'Jesús Baños Sancho', 'jbs', 'ag111', 8, 1, 11, NULL);
insert into agentes values (1111, 'Salvador Romero Villegas', 'srv', 'ag1111', 7, 1, 111, NULL);
insert into agentes values (1112, 'José Javier Bermúdez Hernández', 'jjbh', 'ag1112', 7, 1, 111, NULL);
insert into agentes values (1113, 'Alfonso Bonillo Sierra', 'abs', 'ag1113', 7, 1, 111, NULL);
insert into agentes values (1121, 'Silvia Thomas Barrós', 'stb', 'ag1121', 7, 1, 112, NULL);
insert into agentes values (11211, 'Ernesto Osoro Gorrotxategi', 'eog', 'ag11211', 5, 0, 1121, NULL);
insert into agentes values (11212, 'Guillermo Campos Guillén', 'gcg', 'ag11212', 5, 0, 1121, NULL);
insert into agentes values (11213, 'Antonio Fernández Ruíz', 'afr', 'ag11213', 5, 0, 1121, NULL);
insert into agentes values (11214, 'María Luisa López Caballero', 'mllc', 'ag11214', 5, 0, 1121, NULL);
insert into agentes values (11221, 'Virginia Morenas Rubio', 'vmr', 'ag11221', 5, 0, 1121, NULL);
insert into agentes values (11222, 'Rosario Castro García', 'rcg', 'ag11222', 5, 0, 1122, NULL);
320
insert into agentes values (11223, 'Antonio Álvarez Palomeque', 'aap', 'ag11223', 5, 0, 1122, NULL);
insert into agentes values (11224, 'David Martínez Martínez', 'dmm', 'ag11224', 5, 0, 1122, NULL);
insert into agentes values (11225, 'Juan Corral González', 'jcg', 'ag11225', 5, 0, 1122, NULL);
insert into agentes values (11226, 'Eduardo Alfada Pereira', 'eap', 'ag11226', 5, 0, 1122, NULL);
insert into agentes values (11231, 'Cayetano García Herrera', 'cgh', 'ag11231', 5, 0, 1123, NULL);
insert into agentes values (11232, 'José Antonio Sieres Vega', 'jasv', 'ag11232', 5, 0, 1123, NULL);
insert into agentes values (11233, 'Juan Manuel Guzmán García', 'jmgg', 'ag11233', 5, 0, 1123, NULL);
commit;
321
Excepciones predefinidas en Oracle.
Cuando ejecutamos varias sentencias seguidas del mismo tipo y queremos capturar alguna
posible excepción debida al tipo de sentencia, podemos encapsular cada sentencia en un bloque
y manejar en cada bloque la excepción, o podemos utilizar una variable localizadora para saber
qué sentencia ha sido la que ha lanzado la excepción (aunque de esta manera no podremos
continuar por la siguiente sentencia).
DECLARE
sentencia NUMBER := 0;
BEGIN
...
SELECT * FROM agentes ...
sentencia := 1;
SELECT * FROM familias ...
Sentencia := 2;
SELECT * FROM oficinas ...
...
EXCEPTION
WHEN NO_DATA_FOUND THEN
IF sentencia = 0 THEN
RAISE agente_no_encontrado;
ELSIF sentencia = 1 THEN
RAISE familia_no_encontrada;
ELSIF sentencia = 2 THEN
RAISE oficina_no_encontrada;
END IF;
END;
322
Si la excepción es capturada por un manejador de excepción apropiado, ésta es tratada y
posteriormente el control es devuelto al bloque superior. Si la excepción no es capturada y no
existe bloque superior, el control se devolverá al entorno. También puede darse que la excepción
sea manejada en un bloque superior a falta de manejadores para ella en los bloques internos, la
excepción se propaga de un bloque al superior y así hasta que sea manejada o no queden
bloques superiores con lo que el control se devuelve al entorno. Una excepción también puede
ser relanzada en un manejador.
Autoevaluación
Todas las excepciones están predefinidas y nosotros no podemos definir nuevas excepciones.
Verdadero.
Falso.
Efectivamente, esta afirmación es falsa.
Verdadero.
Falso.
Efectivamente, las lanzamos explícitamente usando la sentencia RAISE.
Es obligatorio declarar todas las excepciones predefinidas que vamos a usar en nuestros
bloques.
Verdadero.
Falso.
Efectivamente, ni es obligatorio ni debemos hacerlo.
Donde error_number es un entero negativo comprendido entre –20000..-20999 y message es una cadena que
devolvemos a la aplicación. El tercer parámetro especifica si el error se coloca en la pila de errores
(TRUE) o se vacía la pila y se coloca únicamente el nuestro (FALSE). Sólo podemos llamar a este
procedimiento desde un subprograma.
No hay excepciones predefinidas asociadas a todos los posibles errores de Oracle, por lo que nosotros
podremos asociar excepciones definidas por nosotros a errores Oracle, por medio de la directiva al
compilador (o pseudoinstrucción):
323
Donde nombre_excepcion es el nombre de una excepción definida anteriormente, y error_Oracle es el número
negativo asociado al error.
DECLARE
no_null EXCEPTION;
PRAGMA EXCEPTION_INIT(no_null, -1400);
id familias.identificador%TYPE;
nombre familias.nombre%TYPE;
BEGIN
...
nombre := NULL;
...
INSERT INTO familias VALUES (id, nombre, null, null);
EXCEPTION
WHEN no_null THEN
...
END;
Oracle asocia 2 funciones para comprobar la ejecución de cualquier sentencia. SQLCODE nos devuelve
el código de error y SQLERRM devuelve el mensaje de error asociado. Si una sentencia es ejecutada
correctamente, SQLCODE nos devuelve 0 y en caso contrario devolverá un número negativo asociado al
error (excepto NO_DATA_FOUND que tiene asociado el +100).
DECLARE
cod number;
msg varchar2(100);
BEGIN
...
EXCEPTION
WHEN OTHERS THEN
cod := SQLCODE;
msg := SUBSTR(SQLERRM, 1, 1000);
INSERT INTO errores VALUES (cod, msg);
END;
Autoevaluación
De las siguientes afirmaciones marca las que creas que son correctas.
Podemos lanzar nuestros propios mensajes de error a las aplicaciones, acceder al código de error y al
mensaje asociado al mismo generado por la ejecución de una sentencia y asociar excepciones definidas
por nosotros a códigos de error de Oracle.
324
Anexo III.- Evaluación de los atributos de un
cursor explícito.
Notación mixta.
DECLARE
PROCEDURE prueba( formal1 NUMBER, formal2 VARCHAR2) IS
BEGIN
...
END;
actual1 NUMBER;
actual2 VARCHAR2;
BEGIN
...
prueba(actual1, actual2); --posicional
prueba(formal2=>actual2,formal1=>actual1); --nombrada
prueba(actual1, formal2=>actual2); --mixta
325
END;
Parámetros de entrada.
Parámetros de salida.
DECLARE
SUBTYPE familia IS familias%ROWTYPE;
SUBTYPE agente IS agentes%ROWTYPE;
SUBTYPE tabla_agentes IS TABLE OF agente;
familia1 familia;
familia2 familia;
hijos_fam tabla_agentes;
FUNCTION inserta_familia( mi_familia familia,
mis_agentes tabla_agentes := tabla_agentes() )
RETURN NUMBER IS
BEGIN
INSERT INTO familias VALUES (mi_familia);
FOR i IN 1..mis_agentes.COUNT LOOP
IF (mis_agentes(i).oficina IS NOT NULL) or (mis_agentes(i).familia ¡= mi_familia.identificador)
THEN
ROLLBACK;
RETURN –1;
END IF;
INSERT INTO agentes VALUES (mis_agentes(i));
END LOOP;
COMMIT;
RETURN 0;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ROLLABACK;
326
RETURN –1;
WHEN OTHERS THEN
ROLLBACK;
RETURN SQLCODE;
END inserta_familia;
BEGIN
...
resultado := inserta_familia( familia1 );
...
resultado := inserta_familia( familia2, hijos_fam2 );
...
END;
DECLARE
TYPE agente IS agentes%ROWTYPE;
TYPE familia IS familias%ROWTYPE;
TYPE tAgentes IS TABLE OF agente;
TYPE tFamilias IS TABLE OF familia;
mi_familia familia;
mi_familia1 familia;
familias_hijas tFamilias;
mi_familia2 familia;
hijos tAgentes;
BEGIN
...
resultado := inserta_familia(mi_familia);
...
resultado := inserta_familia(mi_familia1, familias_hijas);
...
328
resultado := inserta_familia(mi_familia2, hijos);
...
END;
DECLARE
TYPE agente IS agentes%ROWTYPE;
TYPE tAgentes IS TABLE OF agente;
hijos10 tAgentes;
PROCEDURE dame_hijos( id_familia NUMBER,
hijos IN OUT tAgentes ) IS
CURSOR hijas IS SELECT identificador FROM familias WHERE familia = id_familia;
hija NUMBER;
CURSOR cHijos IS SELECT * FROM agentes WHERE familia = id_familia;
hijo agente;
BEGIN
--Si la tabla no está inicializada -> la inicializamos
IF hijos IS NULL THEN
hijos = tAgentes();
END IF;
--Metemos en la tabla los hijos directos de esta familia
OPEN cHijos;
LOOP
FETCH cHijos INTO hijo;
EXIT WHEN cHijos%NOTFOUND;
hijos.EXTEND;
hijos(hijos.LAST) := hijo;
END LOOP;
CLOSE cHijos;
--Hacemos lo mismo para todas las familias hijas de la actual
OPEN hijas;
LOOP
FETCH hijas INTO hija;
EXIT WHEN hijas%NOTFOUND;
dame_hijos( hija, hijos );
END LOOP;
CLOSE hijas;
END dame_hijos;
BEGIN
...
dame_hijos( 10, hijos10 );
...
END;
329
Anexo VII.- Ejemplo de paquete.
Aquí puedes ver un ejemplo de un paquete que agrupa las principales tareas que llevamos a cabo con
la base de datos de ejemplo.
--Inserta un agente
FUNCTION inserta_agente ( mi_agente agente )
RETURN NUMBER;
--Borramos un agente
FUNCTION borra_agente( id_agente NUMBER )
RETURN NUMBER;
END call_center;
/
331
padre := NULL;
END oficina_padre;
332
OPEN cHijos;
LOOP
FETCH cHijos INTO hijo;
EXIT WHEN cHijos%NOTFOUND;
hijos.EXTEND;
hijos(hijos.LAST) := hijo;
END LOOP;
CLOSE cHijos;
--hacemos lo mismo para las familias hijas
OPEN cHijas;
LOOP
FETCH cHijas INTO hija;
EXIT WHEN cHijas%NOTFOUND;
dame_hijos( hija, hijos );
END LOOP;
CLOSE cHijas;
EXCEPTION
WHEN OTHERS THEN
hijos := tAgentes();
END dame_hijos;
--Inserta un agente
FUNCTION inserta_agente ( mi_agente agente )
RETURN NUMBER IS
BEGIN
IF (mi_agente.familia IS NULL and mi_agente.oficina IS NULL) THEN
RETURN operacion_no_permitida;
END IF;
IF (mi_agente.familia IS NOT NULL and mi_agente.oficina IS NOT NULL) THEN
RETURN operacion_no_permitida;
END IF;
INSERT INTO agentes VALUES (mi_agente.identificador, mi_agente.nombre, mi_agente.usuario,
mi_agente.clave, mi_agente.habilidad, mi_agente.categoria, mi_agente.familia, mi_agente.oficina );
COMMIT;
RETURN todo_bien;
EXCEPTION
WHEN referencia_no_encontrada THEN
ROLLBACK;
RETURN padre_inexistente;
WHEN no_null THEN
ROLLBACK;
RETURN no_null_violado;
WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK;
RETURN elemento_existente;
WHEN OTHERS THEN
ROLLBACK;
RETURN SQLCODE;
END inserta_agente;
333
RETURN NUMBER IS
BEGIN
IF (mi_familia.familia IS NULL and mi_familia.oficina IS NULL) THEN
RETURN operacion_no_permitida;
END IF;
IF (mi_familia.familia IS NOT NULL and mi_familia.oficina IS NOT NULL) THEN
RETURN operacion_no_permitida;
END IF;
INSERT INTO familias VALUES ( mi_familia.identificador, mi_familia.nombre, mi_familia.familia,
mi_familia.oficina );
COMMIT;
RETURN todo_bien;
EXCEPTION
WHEN referencia_no_encontrada THEN
ROLLBACK;
RETURN padre_inexistente;
WHEN no_null THEN
ROLLBACK;
RETURN no_null_violado;
WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK;
RETURN elemento_existente;
WHEN OTHERS THEN
ROLLBACK;
RETURN SQLCODE;
END inserta_familia;
334
SELECT COUNT(*) INTO num_ofi FROM oficinas
WHERE identificador = id_oficina;
IF (num_ofi = 0) THEN
RETURN elemento_inexistente;
END IF;
DELETE oficinas WHERE identificador = id_oficina;
COMMIT;
RETURN todo_bien;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RETURN SQLCODE;
END borra_oficina;
--Borramos un agente
FUNCTION borra_agente( id_agente NUMBER )
RETURN NUMBER IS
num_ag NUMBER;
BEGIN
SELECT COUNT(*) INTO num_ag FROM agentes
WHERE identificador = id_agente;
IF (num_ag = 0) THEN
RETURN elemento_inexistente;
END IF;
DELETE agentes WHERE identificador = id_agente;
COMMIT;
RETURN todo_bien;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RETURN SQLCODE;
END borra_agente;
END call_center;
335