Triggers en Oracle Nuestro objetivo para este artículo es intentar mostrar un breve panorama de cómo crear TRIGGERS (disparadores o gatilladores [1]) para bases de datos bajo Oracle 8, asi como describir la funcionalidad tan versatil que proporcionan al programador. Por lo tanto es de esperar que el lector tenga un poco de experiencia con entornos Oracle y conozca la notación básica de bases de datos, de igual manera serán necesarios unos mínimos conocimientos del lenguaje PL/SQL. 1. Definición de trigger Los disparadores son procedimientos que se ejecutan cuando se produce un suceso de base de datos (una operación DML: INSERT, UPDATE o DELETE) en una tabla específica. El acto de ejecutar un disparador se conoce como disparo. 2. Uso de los triggers Los disparadores pueden emplearse para muchas cosas diferentes, incluyendo: • El mantenimiento de restricciones de integridad complejas, que no sean posibles con las restricciones declarativas definidas en el momento de crear la tabla. • La auditoría de la información contenida en una tabla, registrando los cambios realizados y la identidad del que los llevó a cabo. • El aviso automático a otros programas de que hay que llevar a cabo una determinada acción, cuando se realiza un cambio en una tabla. 3. Sintaxis general de trigger Lo presentado en este apartado se ha extraído desde [2]. La sintaxis general para crear un disparador es: CREATE [OR REPLACE] TRIGGER nombre_disparador { BEFORE | AFTER ? suceso_disparo ON referencia_tabla [FOR EACH ROW [ WHEN condición_disparo ]] cuerpo_disparador, donde nombre_disparador es el nombre del disparador, suceso_disparo especifica cuándo se activa el disparador, referencia_tabla es la tabla para la cual se define el disparador y cuerpo_disparador es el código principal del disparador. Antes se evalúa la condición_disparo incluida en la cláusula WHEN, si es que está presente. El cuerpo del disparador se ejecuta sólo cuando dicha condición se evalúa como verdadera. 4. Componentes de un disparador Los componentes de un disparador son el nombre, el suceso de disparo y el cuerpo. La cláusula WHEN es opcional. powered by Jorge Muñoz 1 5.1 Nombres de disparadores El espacio de nombres para los disparadores es diferente del de otros subprogramas. El espacio de nombres es el conjunto de identificadores válidos que pueden emplearse como nombres de un objeto. Los disparadores existen en un espacio de nombres separado del de los procedimientos, paquetes y tablas, por lo que un disparador puede tener el mismo nombre que una tabla o procedimiento. Sin embargo dentro de un mismo esquema deben tener nombres diferentes entre sí. 5.2 Tipos de disparadores El suceso de disparo determina el tipo de disparador. Los disparadores pueden definirse para las operaciones INSERT, UPDATE o DELETE, y pueden dispararse antes o después de la operación. Finalmente, el nivel de los disparadores puede ser la fila o la orden. La Figura 1 muestra los tipos de disparadores: Categoría Valores Comentarios INSERT, Define qué tipo de orden DML provoca la Orden DELETE, activación del disparador. UPDATE Define si el disparador se activa antes o BEFORE Temporización después de que se ejecute la orden o AFTER (disparador previo o posterior). Los disparadores con nivel de fila se activan una vez por cada fila afectada por la orden que provocó el disparo. Los Fila u disparadores con nivel de orden se activan Nivel orden sólo una vez, antes o después de la orden. Los disparadores con nivel de fila se identifican por la cláusula FOR EACH ROW en la definición del disparador. Figura 1. Tipos de disparadores [2] Disparadores de sustitución Oracle 8 proporciona un tipo adicional de disparador. Los disparadores de sustitución ( INSTEAD OF ) pero tienen una serie de restricciones [3]: • INSTEAD OF es una cláusula válida solo para vistas; no se puede especificar un disparador INSTEAD OF en una tabla. • Si una vista tiene un disparador INSTEAD OF, cualquier vista creada sobre ésta debe tener a su vez un disparador INSTEAD OF. • Cuando definimos disparadores INSTEAD OF para columnas LOB, podemos leer powered by Jorge Muñoz 2 tanto el seudo-registro :OLD como el seudo-registro :NEW, pero no se puede modificar sus valores. 5.3 Restricciones de los disparadores El cuerpo de un disparador es un bloque PL/SQL. Cualquier orden que sea legal en un bloque PL/SQL, es legal en el cuerpo de un disparador, con las siguientes restricciones: 1. Un disparador no puede emitir ninguna orden de control de transacciones: COMMIT, ROLLBACK o SAVEPOINT. El disparador se activa como parte de la ejecución de la orden que provocó el disparo, y forma parte de la misma transacción que dicha orden. Cuando la orden que provoca el disparo es confirmada o cancelada, se confirma o cancela también el trabajo realizado por el disparador. 2. Por razones idénticas, ningún procedimiento o función llamado por el disparador puede emitir órdenes de control de transacciones. 3. El cuerpo del disparador no puede contener ninguna declaración de variables LONG o LONG RAW. 4. Existen restricciones acerca de a qué tablas puede acceder el cuerpo de un disparador. Dependiendo del tipo de disparador y de las restricciones que afecten a las tablas. 5. Eliminación y deshabilitación de los disparadores La sintaxis de la orden que elimina un disparador es [4]: DROP TRIGGER nombre_disparador; donde nombre_disparador es el nombre del disparador que se desea eliminar. Esta orden elimina el disparador de forma permanente del diccionario de datos. La sintaxis de la orden que deshabilita un disparador es [4]: ALTER TRIGGER nombre_disparador [DISABLE | ENABLE] donde nombre_disparador es el nombre del disparador. Todos los disparadores están habilitados en el momento de su creación. Cuando se deshabilita un disparador continua existiendo en el diccionario de datos. También se puede habilitar o deshabilitar todos los disparadores de una tabla determinada, utilizando la orden ALTER TABLE con la cláusula ENABLE ALL TRIGGERS o DISABLE ALL TRIGGERS. 6. Orden de activación de los disparadores Lo presentado en este apartado se ha extraído desde [2]. Los disparadores se activan al ejecutarse la orden DML. El algoritmo de ejecución de una orden DML es el siguiente: 1. Ejecutar, si existe, el disparador de tipo BEFORE (disparador previo) con nivel de powered by Jorge Muñoz 3 orden. 2. Para cada fila a la que afecte la orden: a. Ejecutar, si existe, el disparador de tipo BEFORE con nivel de fila. b. Ejecutar la propia orden. c. Ejecutar, si existe, el disparador de tipo AFTER (disparador posterior) con nivel de fila. 3. Ejecutar, si existe, el disparador de tipo AFTER con nivel de orden. 7. Utilización de :old y :new en los disparadores con nivel de fila Un disparador con nivel de fila se ejecuta una vez por cada fila procesada por la orden que provoca el disparo. Dentro del disparador puede accederse a la fila que está siendo actualmente procesada utilizando, para ello, dos seudo-registros, :old y :new. La Figura 2 muestra este concepto: Orden de disparo INSERT UPDATE DELETE :old :new No definido; todos los campos toman valor NULL. Valores originales de la fila, antes de la actualización. Valores que serán insertados cuando se complete la orden. Nuevos valores que serán escritos cuando se complete la orden. No definidos; todos los Valores, antes del borrado campos toman el valor de la fila. NULL. Figura 2. :old y :new [2] El tipo de estos seudo-registros es : tabla_disparo%ROWTYPE; donde tabla_disparo es la tabla sobre la que se ha definido el disparador. Los registros :new y :old son sólo válidos dentro de los disparadores con nivel de fila. Si se intenta Hacer referencia a cualquiera de los dos dentro de un disparador con nivel de orden, se obtendrá un error de compilación. Puesto que un disparador con nivel de orden se ejecuta una sola vez, incluso si la orden procesa varias filas, entonces :old y :new no tienen ningún sentido ya que no se sabe a que fila se están refiriendo. 8. La cláusula WHEN Lo presentado en este apartado se ha extraído desde [2]. La cláusula WHEN sólo es válida para los disparadores con nivel de fila. Si está powered by Jorge Muñoz 4 presente, el cuerpo del disparador sólo se ejecutará para las filas que cumplan la condición especificada en la cláusula. La cláusula WHEN tiene la forma: WHEN condición donde condición es una expresión booleana que será evaluada para cada fila. Se puede hacer también referencia a los registros :new y :old dentro de la condición, pero en ese caso no se utilizan los dos puntos. 9. Utilización de predicados de los disparadores: INSERTING, UPDATING y DELETING Dentro de un disparador en el que se disparan distintos tipos de órdenes DML (INSERT, UPDATE y DELETE), hay tres funciones booleanas que pueden emplearse para determinar de qué operación se trata. Estos predicados son INSERTING, UPDATING y DELETING. Su comportamiento es el siguiente: La Figura 3 muestra el comportamiento de INSERTING, UPDATING y DELETING Predicado Comportamiento TRUE si la orden de disparo es INSERT; FALSE en INSERTING otro caso. TRUE si la orden de disparo es UPDATE; FALSE en UPDATING otro caso. TRUE si la orden de disparo es DELETE; FALSE en DELETING otro caso. Figura 3. INSERTING, UPDATING y DELETING [2] 10. Ejemplo Un ejemplo de su uso es proporcionar una facilidad de auditoría donde se realiza automáticamente un registro de actividades siempre que se cambia una fila de una tabla. Sin los disparadores de bases de datos esta funcion sería implementada en los programas de seccion de entrada (front-end) que realizan el cambio en la base de datos; sin embargo alguien que se pase por alto el código de los programas de sección de entrada (utilizando SQL*Plus, por ejemplo) no pasaría por las comprobaciones y el procesamiento definidos. Supóngase que tenemos una tabla llamada SAL (en la que almacenamos los salarios de los empleados de la empresa) y resulta necesario conocer cuándo esta siendo accedida la tabla y el tipo de operación que se realiza. El ejemplo que presentamos a continuación contiene un paquete de muestra que rastrea esta información registrando la hora y la accion ejecutada (UPDATE, DELETE, o INSERT) en la tabla SAL. Mediante una variable global, STAT.ROWCNT, inicializada a cero por el trigger BEFORE e incrementada cada vez que un disparador con nivel de fila es ejecutado, tenemos la powered by Jorge Muñoz 5 informacion estadística que necesitamos salvar en el disparador AFTER. DROP TABLE stat_tab; CREATE TABLE stat_tab(utype CHAR(8), rowcnt INTEGER, uhour INTEGER); CREATE OR REPLACE PACKAGE stat IS rowcnt INTEGER; END; CREATE TRIGGER bt BEFORE UPDATE OR DELETE OR INSERT ON sal BEGIN stat.rowcnt := 0; END; CREATE TRIGGER rt BEFORE UPDATE OR DELETE OR INSERT ON sal FOR EACH ROW BEGIN stat.rowcnt := stat.rowcnt + 1; END; CREATE TRIGGER at AFTER UPDATE OR DELETE OR INSERT ON sal DECLARE typ CHAR(8); hour NUMBER; BEGIN IF updating THEN typ := 'update'; END IF; IF deleting THEN typ := 'delete'; END IF; IF inserting THEN typ := 'insert'; END IF; hour := TRUNC((SYSDATE - TRUNC(SYSDATE)) * 24); UPDATE stat_tab SET rowcnt = rowcnt + stat.rowcnt WHERE utype = typ AND uhour = hour; IF SQL%ROWCOUNT = 0 THEN INSERT INTO stat_tab VALUES (typ, stat.rowcnt, hour); END IF; EXCEPTION WHEN dup_val_on_index THEN UPDATE stat_tab SET rowcnt = rowcnt + stat.rowcnt WHERE utype = typ AND uhour = hour; powered by Jorge Muñoz 6 END; 11. Conclusiones Las restriciones de integridad complejas que refieren a varias tablas y atributos no pueden ser especificadas en las definiciones de tablas. En cambio, los triggers proveen una técnica procedural para especificar y mantener restricciones de integridad. Permiten especificar restricciones más complejas ya que un trigger es esencialmente un procedimiento PL/SQL. Ahora bien, si es posible, deben usarse restricciones para la comprobacion; son más rápidas que usar disparadores de base de datos. Gracias a la posibilidad de tener multiples disparadores del mismo tipo para el mismo comando sobre cualquier tabla se permite la instalacion modular de aplicaciones que tienen triggers sobre las mismas tablas. En un trigger no se debe incluir mucho codigo sino que resulta mas eficiente poner los segmentos de codigo mas largos dentro de un procedimiento y, después, llamar al procedimiento desde el disparador de la base de datos (los procedimientos son almacenados en una forma compilada). Los triggers son administrados de manera centralizada. Se codifican una sola vez, se prueban y se fuerza a que todas las aplicaciones pasen por su control. Los triggers deben ser controlados, o al menos auditados, por un administrador de BDs powered by Jorge Muñoz 7