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.
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 |
| Orden |
INSERT, DELETE, UPDATE |
Define qué tipo de orden DML provoca la activación del disparador. |
| Temporización |
BEFORE o AFTER |
Define si el disparador se activa antes o después de que se ejecute
la orden (disparador previo o posterior). |
| Nivel |
Fila u orden |
Los disparadores con nivel de fila se activan una vez por cada fila afectada
por la orden que provocó el disparo. Los disparadores con nivel de
orden se activan 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 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 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 |
:old |
:new |
| INSERT |
No definido; todos los campos toman valor NULL. |
Valores que serán insertados cuando se complete la orden. |
| UPDATE |
Valores originales de la fila, antes de la actualización. |
Nuevos valores que serán escritos cuando se complete la orden. |
| DELETE |
Valores, antes del borrado de la fila. |
No definidos; todos los campos toman el valor 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á 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 |
| INSERTING |
TRUE si la orden de disparo es INSERT; FALSE en otro caso. |
| UPDATING |
TRUE si la orden de disparo es UPDATE; FALSE en otro caso. |
| DELETING |
TRUE si la orden de disparo es DELETE; FALSE en 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 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;
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
12. Referencias
[1] http://sol.inf.udec.cl/~mvaras/papers/valdivia/valdivia.htm
Una Propuesta para la Especificación de Restricciones Dinámicas
en Esquemas Conceptuales de Bases de Datos.
[2] Urman, S. (1998). ORACLE 8. Programación en PL/SQL. Osborne McGraw-Hill,
Madrid.
[3] http://oradoc.photo.net/ora81/DOC/server.815/a67779/ch4h.htm
Oracle8i SQL Reference Release 8.1.5.
[4] La Biblia de Oracle8, Advanced Information Systems, Inc. Anaya Multimedia.
[5] http://www.dbmaker.com/reference/manuals/dba/dba_chap_09.html
Database Administrator's Reference - DBMaker Online / Manuals.