Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Autor:
Eduardo Bottini
ebottini@c-s-i.com.ar
A partir de este número de Mundo Delphi comenzamos con una serie de artículos que tratan sobre las
características más potentes del lenguaje PL/SQL de Oracle. En esta edición, donde ahondaremos en los
detalles del tema del título principal, veremos también algunos de estos atributos tales como el manejo de
excepciones y la programación utilizando paquetes (packages), temas éstos que profundizaremos en
próximas entregas.
Para crear este tipo de variables debe definirse primero un tipo Ref Cursor y luego declarar la variable de
este tipo. Por ejemplo:
En este pequeño ejemplo hemos declarado Res como una variable de tipo cursor.
En PL/SQL, cualquier puntero posee el tipo de dato REF (que es la abreviatura de Reference) y la clase
de objeto a la que apunta. De allí que un Ref Cursor sea un puntero a un Cursor.
Tipo restrictivo: Se debe especificar un tipo de resultado asociado al formato del registro que devolverá
el Select. En este caso el compilador de PL/SQL controla que el resultado de la consulta coincida con el
tipo de dato asociado. Por ejemplo, si asociamos el resultado con un %RowType (tipo de registro que
representa a una fila de una tabla) sólo podremos utilizar la variable cursor con una consulta que devuelva
un registro exactamente igual a ese %RowType.
Tipo no Restrictivo: A partir de la versión 7.3 de Oracle no es necesario especificar un tipo de resultado,
pudiéndose de esta manera asociar un puntero a cualquier clase de consulta. Esta forma es mucho más
flexible ya que podremos declarar los punteros una vez y utilizarlos en cualquier momento para manipular
cualquier consulta.
Al tipo de resultado apuntando por un Ref Cursor no restrictivo se lo denomina Variant Record.
1
Oracle PL/SQL – Variables de tipo Cursor.
En el siguiente ejemplo detallaremos cómo definir las dos clases de Ref Cursor que vimos anteriormente.
En el primer caso, REFCURS_AUTORES es de tipo restrictivo ya que las variables declaradas bajo este
tipo de dato deberán apuntar obligatoriamente a cursores de un tipo de dato equivalente. En este ocasión
el tipo de dato coincide con el formato de registro de la tabla Autores (RowType). Si se intentara utilizar
una variable de este tipo para apuntar a un cursor no coincidente con la definición de la misma, el
compilador de PL/SQL nos indicará el error.
El segundo tipo de Ref Cursor, REFCURS, corresponde a uno no restrictivo y tal como veremos en los
ejemplos puede utilizarse para apuntar a cualquier tipo de cursor de datos.
Para realizar la definición de ambos tipos utilizaremos un encabezado de Paquete. Esto nos permitirá que
nuestra definición de tipos sea global y podamos utilizarla en cualquier procedimiento o función
almacenada dentro del servidor.
Una vez definidos los tipos podemos declarar las variables y utilizarlas. En el siguiente ejemplo
crearemos una función que utiliza el tipo restrictivo de variable para retornar un puntero que referencia a
un cursor cuya fuente de datos es una selección de la tabla Autores.
2
Oracle PL/SQL – Variables de tipo Cursor.
Luego de creado, para ejecutarlo desde el SqlPlus de Oracle debe primero declararse dentro de este
entorno una variable de tipo RefCursor que nos servirá como repositorio del puntero devuelto por la
función.
Para declarar la variable, que nos servirá también para el resto de los ejemplos, debe escribirse:
VAR R RefCursor;
EXEC :R := FRC_TRAE_AUTORES;
Para ver el resultado de la consulta debemos mostrar el contenido del puntero R. Para esto se utiliza el
comando PRINT, de la siguiente forma:
PRINT R;
Otra forma de programar el ejemplo anterior es mediante un procedimiento almacenado en lugar de una
función. En este caso veremos cómo debe recibirse la variable de tipo cursor en forma de parámetro de
entrada/salida (IN OUT) ya que a través de la misma se recibirá/retornará el valor del puntero. Esta
variable debe encontrase previamente declarada en el programa llamador (en este caso el SqlPlus, nuestra
ya declarada variable R).
Para ejecutarlo, se debe llamar al procedimiento almacenado enviando como parámetro la variable R, y
luego mostramos el contenido de la misma con el comando PRINT.
Ahora mostraremos un ejemplo de uso del tipo no restrictivo. A esta nueva función le pasaremos como
parámetro una señal que indicará sobre cual tabla queremos que realice la consulta: Autores o Libros.
Al ser la variable Res del tipo Ref Cursor no restrictivo puede apuntar tanto a uno como a otro cursor
indistintamente, dependiendo de la señal A_O_L que le es enviada a la función.
3
Oracle PL/SQL – Variables de tipo Cursor.
Para ejecutarlo:
EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('L');
EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('A');
Y luego:
PRINT R;
Dependiendo del parámetro enviado, R nos mostrará el resultado de la selección de la tabla Autores o de
la tabla Libros.
Seguridad mejorada: Los usuarios sólo necesitan tener permisos de ejecución de los procedimientos y
funciones que realizan las consultas, no siendo necesario que tengan permisos de acceso a las tablas que
estos procedimientos consultan. De esta manera se evitan acc esos indebidos o no deseados a la
información por fuera de las restricciones que imponen nuestras aplicaciones.
4
Oracle PL/SQL – Variables de tipo Cursor.
comodines al parámetro recibido.
Finalmente veremos cómo ejecutar esta función desde otro procedimiento PL/SQL y desde un programa
Delphi.
DECLARE
-- declaración de un tipo de registro donde guardar cada fila de la consulta
TYPE tRecRes IS RECORD (
codigo_libro LIBROS.CODIGO%TYPE,
nombre_libro LIBROS.NOMBRE%TYPE,
fecha_edicion LIBROS.FECHA_EDICION%TYPE,
prestado LIBROS.PRESTADO%TYPE,
nombre_autor AUTORES.NOMBRE%TYPE,
fecha_nacimiento AUTORES.FECHA_NACIMIENTO%TYPE
);
5
Oracle PL/SQL – Variables de tipo Cursor.
BEGIN
-- Llamamos a la función que abre la consulta y nos devuelve un puntero a la misma
-- A esta función le enviamos como parámetros parte del nombre del autor y una fecha
-- de edición base, con el fin de filtrar la consulta.
END;
/
6
Oracle PL/SQL – Variables de tipo Cursor.
7
Oracle PL/SQL – programación con Paquetes.
Siguiendo con nuestro tutorial sobre elementos avanzados de programación Oracle PL/Sql, en esta
oportunidad veremos cómo embeber procedimientos, funciones, definiciones de tipos de datos y
declaraciones de variables en una misma estructura que los agrupe y relacione lógicamente. Esta
estructura se denomina Package (Paquete) y su uso nos permite no sólo mejorar la calidad de diseño de
nuestras aplicaciones sino también optimizar el desempeño de las mismas.
Packages.
Como vimos, un Paquete es un objeto PL/Sql que agrupa lógicamente otros objetos PL/Sql relacionados
entre sí, encapsulándolos y convirtiéndolos en una unidad dentro de la base de datos.
Los Paquetes están divididos en 2 partes: especificación (obligatoria) y cuerpo (no obligatoria). La
especificación o encabezado es la interfaz entre el Paquete y las aplicaciones que lo utilizan y es allí
donde se declaran los tipos, variables, constantes, excepciones, cursores, procedimientos y funciones que
podrán ser invocados desde fuera del paquete.
En el cuerpo del paquete se implementa la especificación del mismo.
La especificación contiene las declaraciones públicas, o sea, la declaración de elementos que son visibles
a las aplicaciones que tienen acceso a ese mismo esquema dentro de la base de datos.
El cuerpo contiene los detalles de implementación y declaraciones privadas, manteniéndose todo ésto
oculto a las aplicaciones externas, siguiendo el conocido concepto de “caja negra”.
Dicho de otra manera, sólo las declaraciones hechas en la especificación del paquete son visibles y
accesibles desde fuera del paquete (por otras aplicaciones o procedimientos almacenados) quedando los
detalles de implementación del cuerpo del paquete totalmente ocultos e inaccesibles para el exterior.
Para acceder a los elementos declarados en un paquete basta con anteceder el nombre del objecto a
referenciar con el nombre del paquete donde está declarado y un punto, de esta manera:
Paquete.Objeto
donde Objeto puede ser un tipo, una variable, un cursor, un procedimiento o una función declarados
dentro del paquete.
Esta notación es válida tanto para referenciar desde procedimientos o triggers externos al paquete como
desde aplicaciones escritas en otros lenguajes o mismo desde herramientas como el Sql*Plus.
Para referenciar objetos desde adentro del mismo paquete donde han sido declarados no es necesario
especificar el nombre del paquete y pueden (deberían) ser referenciados directamente por su nombre.
Finalmente y siguiendo a la parte declarativa del cuerpo de un paquete puede, opcionalmente, incluirse la
sección de inicialización del paquete. En esta sección pueden, por ejemplo, inicializarse variables que
8
Oracle PL/SQL – programación con Paquetes.
utiliza el paquete tal como veremos en el ejemplo final. La sección de inicialización se ejecuta sólo la
primera vez que una aplicación referencia a un paquete, ésto es, se ejecuta sólo una vez por sesión.
Un ejemplo.
A continuación veremos un ejemplo de los conceptos explicados anteriormente.
El objetivo de esta tabla, Accesos, es llevar un registro por cada acceso inválido que realice un usuario en
cada sesión, y un histórico total acumulativo a fines de facilitar la lectura de la historia. Supondremos que
la función Alta será invocada por una aplicación que supervisa esta tarea de control.
9
Oracle PL/SQL – programación con Paquetes.
Alta es la única función pública del paquete.
--
-- Usuario: Usuario Oracle.
-- Fecha_Hora_Inicio_Sesion: Momento en que comienza la sesión actual.
-- Terminal_Sesion: Terminal desde donde se encuentra conectado
-- el usuario.
-- Fecha_Hora_Acceso: Momento en que se produce el acceso inválido.
-- Nro_Acceso_En_Sesion: Cuenta el número de acceso inválido
-- en la sesión actual.
-- Total_Historico: Cuenta el total de accesos inválidos que se
-- registran históricamente para este usuario.
y ahora el Paquete:
10
Oracle PL/SQL – programación con Paquetes.
RETURN 1;
EXCEPTION
WHEN OTHERS THEN RETURN 0;
-- si se produce algun error
-- retorna cero
END;
11
Oracle PL/SQL – Desarrollo de funciones SQL
En esta entrega veremos las reglas que deben
seguir las funciones almacenadas para que
puedan usarse en una sentencia SQL
Una de las caractéristicas que hacen sobresalir a Oracle por sobre otros administradores de bases de
datos relacionales es la enorme cantidad de funciones que vienen “de fábrica” para poder usarse
directamente dentro de sentencias Sql. Esta funciones son de conversión de tipos, aritméticas, de manejo
de cadenas de caracteres, de manejo de fechas, etc.
No obstante, siempre podremos necesitar desarrollar nuestras propias funciones para casos
específicos y poder adicionarlas a la librería preexistente.
Restricciones
Para ejecutar una sentencia Sql que invoca a una función almacenada, Oracle debe conocer
anticipadamente la posibilidad de que esta función produzca alguna modificación en el estado actual de la
base de datos, para evitar así un resultado inesperado en el retorno de la función.
Para minimizar esta posibilidad, Oracle inhibe a las funciones SQL de:
• Modificar datos en tablas, por lo tanto no puede invocar sentencias Insert, Update ni Delete.
• Modificar variables globales dentro de un Paquete.
• Contener parámetros OUT o IN OUT.
• Ser una función de columna o grupo. Las funciones de columna son aquélas que toman como
entrada toda una columna de datos, como por ejemplo SUM(), o AVG(). Por lo tanto nuestras
funciones sólo podrán comportarse como funciones de fila.
Un ejemplo
Para nuestro ejemplo desarrollaremos una función que respete las reglas arriba enumeradas. Dentro de un
Paquete PL/Sql crearemos una función de validación de CUIT. Para los que no vivan en la República
Argentina o no estén familiarizados con esta sigla les cuento que CUIT significa Código Unico de
Identificación Tributaria y es utilizado por el organis mo recaudador de impuestos para identificar a las
empresas y profesionales. Este número está compuesto por un prefijo de 2 dígitos, un número de 8
posiciones (que en el caso de las personas es el documento nacional de identidad) y un dígito verificador.
Este dígito verificador se calcula mediante una fórmula de módulo 11.
Adentrémonos ahora en la situación que usaremos en nuestro ejemplo. Tendremos una tabla pecargada
con datos de personas (figura 1), donde consta su CUIT. En dicha carga no se ha utilizado ninguna
validación por lo que deberemos evaluar la validez o no de los datos existentes mediante nuestra función
(figura 2).
12
Oracle PL/SQL – Desarrollo de funciones SQL
Figura 1 . Tabla de Personas
EXCEPTION
-- En caso de error retornamos 0
WHEN OTHERS THEN
RETURN 0;
END;
END;
/
Lo primero que haremos será chequear el correcto funcionamiento de la función de validación que hemos
creado, que nos devolverá 1 en caso de recibir un número correcto ó 0 en caso contrario. Para chequearla
la ejecutaremos en la forma estándar, es decir, mediante la instrucción Exec. (figura 3)
Vemos que funciona correctamente, ya que le hemos enviado un número válido y nos devuelve un 1.
VALIDO
---------
1
SQL>
SQL>
En contra de lo esperado, se ha producido un error. El servidor Oracle nos informa que no puede ejecutar
la función dentro de un SQL porque la misma no garantiza que la base de datos no será modificada.
Pero si analizamos el código de nuestra función, veremos que la misma no viola ninguna de las
restricciones conocidas. Entonces, ¿qué pasó?
14
Oracle PL/SQL – Desarrollo de funciones SQL
La clásula Pragma Restrict_References debe ir en el encabezado del Paquete, y siguiendo a la definición
de cada función, siendo la sintaxis la desplegada en la figura 5. En la figura 6 mostramos el significado de
cada argumento de este Pragma.
RNDS
No lee tablas en la base de datos
Reads no database state
El único argumento obligatorio es WNDS, pero siempre es aconsejable definir el máximo nivel de
“pureza” que nuestra función posee, a fin de que el Compilador PL/Sql no rechace innecesariamente a
nuestra función.
Ejemplos de uso.
Finalmente podremos utilizar nuestra Función Almacenada como una Función SQL. En la figura 8
volveremos a nuestro ejemplo de ejecución simple mediante un Select, pero esta vez vemos que
afortunadamente todo termina bien.
15
Oracle PL/SQL – Desarrollo de funciones SQL
Figura 8 . Ejecución exitosa de la función como función SQL
dentro de un Select
SQL> SELECT Pkg_Valida.cuit_valido('20224367911')
SQL> AS VALIDO FROM DUAL;
VALIDO
---------
1
SQL>
Y por último 2 ejemplos más a fin de ilustrar los posibles usos de nuestra función en un contexto como el
planteado, en donde debemos analizar o mostrar el contenido de una tabla cuyos datos no están
validados.
SQL>
INVALIDOS
---------
1
SQL>
16
Oracle PL/SQL – SQL dinámico
De por sí, Oracle no soporta en forma nativa el uso de operaciones dinámicas en un procedimiento o
función almacenada. Al decir “operaciones dinámicas” me refiero a todo aquéllo que deba ser resuelto
en tiempo de ejecución en lugar de en tiempo de compilación. Por ejemplo, si quisiéramos crear una tabla
dentro de un procedimiento almacenado, no podríamos hacerlo mediante el uso de Pl/Sql estándard.
Veamos primero porqué existe esta limitación y luego cómo Oracle nos permite resolver las cosas para
poder llevar a cabo este tipo de tareas.
Por otro lado, un modelo dinámico sería aquél en donde se efectuarían las resoluciones en tiempo de
ejecución, priorizándose de esta manera la flexibilidad de los procesos que pueden desarrollarse dado que
los objetos del esquema pueden no conocerse o no existir hasta el momento de la ejecución de la línea de
código que los referencia.
Dado que el modelo elegido por Oracle es el estático, donde deben existir al momento de la compilación
todos los objetos que se refencian dentro del programa, es que no podemos, como dijimos, crear una tabla
dentro de un procedimiento almacenado usando las sentencias estándares. Esto parece limitante, y lo es,
pero siempre tenemos una forma de hacer las cosas cuando trabajamos con herramientas que han sido
concebidas para darnos satisfacciones en lugar de hacernos pasar penurias. (... apostaría a que estamos
pensando en la misma empresa generadora de penurias ... )
El paquete DBMS_SQL
La forma en la que podemos agregar dinamismo a nuestros procedimientos y funciones almacenadas, es
mediante el uso del paquete SYS.DBMS_SQL.
A través de las funciones implementadas en el mismo podremos realizar operaciones que involucren la
referencia a objetos que no existen o que no podemos determinar al momento de la compilación.
El “truco” consiste en que estas instrucciones no las escribamos como código Pl/Sql sino como “cadenas
de caracteres”, que podrán ser tanto recibidas como parámetros del procedimiento, o ser definidas o
armadas por el mismo, y posteriormente enviadas a las funciones del paquete Dbms_Sql para su efectivo
proceso.
Mediante esta técnica podremos implementar el ejemplo del que hablamos al comienzo: Crear una tabla
dentro de una procedimiento almacenado (“Listado 1”)
El procedimiento utilizado no tiene nada de complejo. Usando las funciones definidas dentro del paquete
Dbms_Sql creamos y abrimos un cursor especial que se usará en la ejecución, armamos la instrucción y la
enviamos para su interpretación y proceso. Luego, cerramos el cursor.
Algo muy parecido a lo que hacemos en Delphi cuando utilizamos TQuery.ExecSql
17
Oracle PL/SQL – SQL dinámico
Tenemos una pequeña empresa que se dedica al ensamblado de computadoras (ordenadores, para mis
amigos españoles). A cada parte que participa en el equipo que finalmente resulta armado la llamaremos
“componente”, siendo a la vez cada componente de un “tipo” específico. Es así que podemos tener
componentes del tipo “teclado”, “monitor”, “chip”, “memoria”, etc.
Imaginemos que dependiendo del modelo de computadora, el tipo de componente a utilizar puede variar,
por ejemplo habrá modelos con monitores de 14 pulgadas y 32 MB de RAM, otros con monitores de 17
pulgadas y 64 MB de memoria, y así.
Bien, lo que la empresa ideó a fin de tener un mejor control de su stock, es una codificación de los
componentes de manera tal que el código del mismo no sea arbitrario sino que esté regido por una regla
definida para el tipo al que pertenece el componente. De esta manera no se podrá codificar erróneamente
un componente, evitándose así errores en su clasificación.
Dado que esta definición es una regla del negocio, la implementaremos en la base de datos. En el Listado
2 veremos las definiciones de las tablas de Tipos_Componente y Componentes.
18
Oracle PL/SQL – SQL dinámico
DESCRIPCION VARCHAR2(50) NOT NULL,
PRIMARY KEY (CODIGO),
FOREIGN KEY (TIPO)
REFERENCES TIPOS_COMPONENTE
);
Como vemos, las tablas están relacionadas por el tipo de componente tal lo esperado. Pero prestemos
atención a la tabla Tipos_Componente. En ella encontramos un campo, Funcion_Validacion, que es el que
guardará la información acerca de cuál función almacenada validará a los códigos de componentes
ingresados en la tabla Componentes. Porque, precisamente, nuestra idea es desarrollar una función que
implemente las reglas de validación para uno o más tipos de componentes, de manera que simplemente
tengamos que relacionar Función de validación con Tipo de Componente a través de este campo de la
tabla. Y luego, la validación se lanzará automáticamente a través de un Trigger que ejecutará a la función
correspondiente en cada momento que se ingrese o modifique un registro en la tabla Componentes.
En el Listado 3 se muestra un ejemplo de datos ingresados en la tabla Tipos_Componente.
COMMIT;
Ahora necesitaremos programar las funciones que realizarán la validación para cada tipo. Según lo
especificado en la tabla, al tipo “monitor” lo validará siempre la función “Monitor_Válido”, al tipo “chip”
la función “Chip_Válido” y el tipo “otro” no será validado. En el Listado 4 crearemos un paquete con las
funciones especificadas. Se han incluído los Pragma necesarios para que estas funciones puedan ser
invocadas como Funciones Sql (tal lo explicado en Mundo Delphi Nº 4, Oracle Pl/Sql - Desarrollo de
funciones SQL).
Para nuestro ejemplo, las funciones de validación serán muy sencillas: verificarán que el tamaño del
código de componente ingresado en la tabla Componentes sea igual a 10, que las 3 primeras posiciones
sean letras (“MON” para el caso de los monitores y “CHI” para el caso de los chips ... bueno, intuyo que
nadie va a sorprenderse por lo ingenioso de ésto, ¿verdad?), y que las siguientes 7 posiciones sean
numéricas y resulten en un valor superior a cero.
Las funciones devolverán 0 en caso de error o 1 en caso contrario.
CREATE OR REPLACE
PACKAGE PKG_VALIDACIONES_TIPOS
AS
FUNCTION MONITOR_VALIDO
(XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER;
PRAGMA RESTRICT_REFERENCES
(MONITOR_VALIDO, WNDS, WNPS, RNPS);
FUNCTION CHIP_VALIDO
(XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER;
PRAGMA RESTRICT_REFERENCES
19
Oracle PL/SQL – SQL dinámico
(CHIP_VALIDO, WNDS, WNPS, RNPS);
END;
/
CREATE OR REPLACE
PACKAGE BODY PKG_VALIDACIONES_TIPOS
AS
FUNCTION MONITOR_VALIDO
(XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER
AS
BEGIN
IF LENGTH(XCODIGO) != 10 OR
SUBSTR(XCODIGO, 1, 3) != 'MON' OR
TO_NUMBER(SUBSTR(XCODIGO, 4, 7)) <= 0 THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
FUNCTION CHIP_VALIDO
(XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER
AS
BEGIN
IF LENGTH(XCODIGO) != 10 OR
SUBSTR(XCODIGO, 1, 3) != 'CHI' OR
TO_NUMBER(SUBSTR(XCODIGO, 4, 7)) <= 0 THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
END;
/
CREATE OR REPLACE
FUNCTION ES_COMPONENTE_VALIDO
(XTIPO TIPOS_COMPONENTE.TIPO%Type,
XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER
AS
C INTEGER;
TMP INTEGER;
nRES NUMBER;
FX TIPOS_COMPONENTE.FUNCION_VALIDACION%Type;
BEGIN
20
Oracle PL/SQL – SQL dinámico
-- Si no la encontramos o es Null
-- entendemos que no está definida
-- y salimos
IF FX IS NULL THEN
RAISE NO_DATA_FOUND;
END IF;
-- Devolvemos el resultado
RETURN nRES;
EXCEPTION
WHEN OTHERS THEN
-- si hubo una excepción cerramos
-- el cursor antes de salir
IF DBMS_SQL.IS_OPEN(C) THEN
DBMS_SQL.CLOSE_CURSOR(C);
END IF;
RETURN 1;
END;
/
21
Oracle PL/SQL – SQL dinámico
EXCEPTION
WHEN eCODIGO_INVALIDO THEN
RAISE_APPLICATION_ERROR(-20999,
CODIGO INVALIDO PARA EL TIPO DE COMPONENTE');
RETURN;
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20999,
'ERROR: ' ||
TO_CHAR(SQLCODE) || ' - ' ||
SUBSTR(SQLERRM, 1, 256));
RETURN;
END;
/
Ahora, cada vez que se inserte o actualice un registro de la tabla Componentes se disparará
automáticamente el Trigger TBUI_Componentes (las siglas “TBUI” las uso por trigger before update
insert). Luego, el Trigger invocará a la función genérica Es_Componente_Valido, enviándole el tipo y
componente ingresados sobre la fila tratada. En caso de recibir de la función el valor 0 (error), nos enviará
una excepción alertándonos sobre tal situación, impidiéndose así que la fila se grabe en la tabla.
Finalmente, en la Figura 7 podremos ver un ejemplo de lo que pasaría si intentamos ingresar un código de
componente no válido para el tipo especificado (empieza con “SON” en lugar de “MON”).
Utilizaremos para la prueba el SqlPlus.
ERROR at line 1:
ORA-20999: CODIGO INVALIDO PARA EL TIPO DE COMPONENTE
ORA-06512: at line 10
ORA-04088: error during execution of trigger ‘TBUI_COMPONENTES'
22