Módulo 07: Triggers (Disparadores)
Objetivos del módulo
- Entender qué son los triggers y cuándo utilizarlos
- Crear triggers DML a nivel de sentencia y a nivel de fila
- Dominar los pseudoregistros
:NEWy:OLD - Utilizar la cláusula
WHENpara disparos condicionales - Crear triggers
INSTEAD OFsobre vistas - Desarrollar triggers de eventos del sistema y DDL
- Gestionar triggers compuestos (compound triggers)
- Habilitar, deshabilitar y eliminar triggers
- Aplicar buenas prácticas y evitar errores comunes
1. ¿Qué es un trigger?
Un trigger (disparador) es un bloque PL/SQL con nombre almacenado en la base de datos que se ejecuta automáticamente cuando ocurre un evento determinado (INSERT, UPDATE, DELETE, DDL, etc.).
A diferencia de los procedimientos y funciones, no se invocan manualmente: Oracle los ejecuta de forma implícita.
| Característica | Procedimiento | Trigger |
|---|---|---|
| Invocación | Manual (EXEC, CALL) | Automática (al ocurrir el evento) |
| Parámetros | Sí | No |
RETURN | Solo funciones | No |
COMMIT / ROLLBACK directo | Sí | No (salvo triggers autónomos) |
| ¿Se puede deshabilitar? | No aplica | Sí (ALTER TRIGGER ... DISABLE) |
📘 Concepto: Los triggers son ideales para auditoría, validaciones de negocio complejas, mantenimiento automático de datos derivados y replicación. No deben usarse como reemplazo de constraints simples ni para lógica de negocio que pertenezca a procedimientos.
2. Tipos de triggers
Oracle soporta varios tipos de triggers:
| Tipo | Evento | Uso principal |
|---|---|---|
| DML | INSERT, UPDATE, DELETE sobre tablas | Auditoría, validación, datos derivados |
| INSTEAD OF | INSERT, UPDATE, DELETE sobre vistas | Hacer modificables vistas complejas |
| DDL | CREATE, ALTER, DROP | Auditar cambios en el esquema |
| De sistema | LOGON, LOGOFF, STARTUP, SHUTDOWN | Seguridad, auditoría de sesiones |
| Compuesto | DML (múltiples puntos de disparo) | Evitar errores de tabla mutante |
3. Triggers DML
3.1. Sintaxis general
CREATE [OR REPLACE] TRIGGER nombre_trigger
{BEFORE | AFTER | INSTEAD OF}
{INSERT | UPDATE [OF columna] | DELETE}
[OR {INSERT | UPDATE [OF columna] | DELETE}]
ON nombre_tabla
[REFERENCING OLD AS old NEW AS new]
[FOR EACH ROW]
[WHEN (condicion)]
DECLARE
-- Declaraciones opcionales
BEGIN
-- Código del trigger
EXCEPTION
-- Manejo de excepciones
END [nombre_trigger];
/
3.2. Momento de disparo: BEFORE vs AFTER
| Momento | Descripción | Uso típico |
|---|---|---|
| BEFORE | Se ejecuta antes de la operación DML | Validar o modificar datos antes de que se guarden |
| AFTER | Se ejecuta después de la operación DML | Auditoría, actualizaciones en cascada |
3.3. Nivel: sentencia vs fila
| Nivel | Sintaxis | Se dispara… |
|---|---|---|
| Sentencia | (sin FOR EACH ROW) | Una vez por sentencia DML |
| Fila | FOR EACH ROW | Una vez por cada fila afectada |
💡 Truco: Si un
UPDATEmodifica 100 filas, un trigger a nivel de sentencia se ejecuta 1 vez, mientras que un triggerFOR EACH ROWse ejecuta 100 veces.
4. Triggers a nivel de sentencia
Se ejecutan una sola vez por cada sentencia DML, independientemente del número de filas afectadas:
-- Trigger que impide operaciones fuera de horario laboral
CREATE OR REPLACE TRIGGER trg_horario_laboral
BEFORE INSERT OR UPDATE OR DELETE
ON conductor
BEGIN
IF TO_CHAR(SYSDATE, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('SAT', 'SUN')
OR TO_NUMBER(TO_CHAR(SYSDATE, 'HH24')) NOT BETWEEN 8 AND 18 THEN
RAISE_APPLICATION_ERROR(-20001,
'Solo se permiten modificaciones en horario laboral (L-V, 8:00-18:00)');
END IF;
END trg_horario_laboral;
/
-- Trigger de auditoría a nivel de sentencia
CREATE OR REPLACE TRIGGER trg_audit_operacion_conductor
AFTER INSERT OR UPDATE OR DELETE
ON conductor
DECLARE
v_operacion VARCHAR2(10);
BEGIN
IF INSERTING THEN
v_operacion := 'INSERT';
ELSIF UPDATING THEN
v_operacion := 'UPDATE';
ELSIF DELETING THEN
v_operacion := 'DELETE';
END IF;
INSERT INTO log_auditoria (accion)
VALUES ('Operación ' || v_operacion || ' sobre CONDUCTOR por ' || USER);
END trg_audit_operacion_conductor;
/
📘 Concepto: Los predicados condicionales
INSERTING,UPDATINGyDELETINGpermiten saber qué operación DML disparó el trigger. Se pueden usar en triggers que responden a múltiples eventos (INSERT OR UPDATE OR DELETE).
5. Triggers a nivel de fila y pseudoregistros :NEW / :OLD
Los triggers FOR EACH ROW tienen acceso a los pseudoregistros :NEW y :OLD:
| Pseudoregistro | INSERT | UPDATE | DELETE |
|---|---|---|---|
:OLD | NULL (no hay fila previa) | Valor antes del cambio | Valor de la fila eliminada |
:NEW | Valor que se va a insertar | Valor después del cambio | NULL (no hay fila nueva) |
5.1. Trigger BEFORE INSERT - Asignar valores automáticos
CREATE OR REPLACE TRIGGER trg_conductor_antes_insert
BEFORE INSERT
ON conductor
FOR EACH ROW
BEGIN
-- Forzar nombre en mayúsculas
:NEW.nombre := UPPER(:NEW.nombre);
-- Si no se indica fecha de carnet, usar la fecha actual
IF :NEW.fec_carnet IS NULL THEN
:NEW.fec_carnet := SYSDATE;
END IF;
END trg_conductor_antes_insert;
/
Prueba:
INSERT INTO conductor (DNI, nombre, fec_nacimiento)
VALUES ('99999999Z', 'juan garcía', TO_DATE('15/06/1990', 'DD/MM/YYYY'));
-- Verificar que se transformó a mayúsculas y se puso fecha de carnet
SELECT nombre, fec_carnet FROM conductor WHERE DNI = '99999999Z';
-- Resultado: JUAN GARCÍA, fecha de hoy
⚠️ Importante: Solo puedes modificar
:NEWen triggersBEFORE. En triggersAFTER, los valores de:NEWson de solo lectura porque la fila ya se ha guardado.
5.2. Trigger AFTER - Auditoría detallada
-- Tabla de auditoría
CREATE TABLE aud_conductor (
aud_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
aud_fecha TIMESTAMP DEFAULT SYSTIMESTAMP,
aud_usuario VARCHAR2(30) DEFAULT USER,
aud_accion VARCHAR2(10),
dni VARCHAR2(15),
nombre_old VARCHAR2(100),
nombre_new VARCHAR2(100)
);
-- Trigger de auditoría
CREATE OR REPLACE TRIGGER trg_audit_conductor_detalle
AFTER INSERT OR UPDATE OR DELETE
ON conductor
FOR EACH ROW
DECLARE
v_accion VARCHAR2(10);
BEGIN
IF INSERTING THEN
v_accion := 'INSERT';
ELSIF UPDATING THEN
v_accion := 'UPDATE';
ELSIF DELETING THEN
v_accion := 'DELETE';
END IF;
INSERT INTO aud_conductor (aud_accion, dni, nombre_old, nombre_new)
VALUES (v_accion,
COALESCE(:NEW.DNI, :OLD.DNI),
:OLD.nombre,
:NEW.nombre);
END trg_audit_conductor_detalle;
/
Prueba:
-- Insertar un registro
INSERT INTO conductor (DNI, nombre, fec_nacimiento, fec_carnet)
VALUES ('88888888X', 'ANA LÓPEZ', TO_DATE('01/01/1985', 'DD/MM/YYYY'), SYSDATE);
-- Actualizar
UPDATE conductor SET nombre = 'ANA LÓPEZ RUIZ' WHERE DNI = '88888888X';
-- Eliminar
DELETE FROM conductor WHERE DNI = '88888888X';
-- Ver auditoría
SELECT aud_accion, dni, nombre_old, nombre_new, aud_fecha
FROM aud_conductor
ORDER BY aud_id;
5.3. Trigger con UPDATE OF (columnas específicas)
-- Solo se dispara cuando se modifica el importe de una multa
CREATE OR REPLACE TRIGGER trg_multa_cambio_importe
BEFORE UPDATE OF importe
ON multa
FOR EACH ROW
BEGIN
IF :NEW.importe > :OLD.importe * 2 THEN
RAISE_APPLICATION_ERROR(-20010,
'No se permite duplicar el importe. Anterior: ' || :OLD.importe ||
', Nuevo: ' || :NEW.importe);
END IF;
DBMS_OUTPUT.PUT_LINE('Importe modificado de ' || :OLD.importe ||
' a ' || :NEW.importe);
END trg_multa_cambio_importe;
/
6. Cláusula WHEN
La cláusula WHEN permite condicionar el disparo del trigger sin entrar en el bloque PL/SQL (más eficiente):
-- Solo auditar cambios en conductores nacidos antes de 1980
CREATE OR REPLACE TRIGGER trg_audit_conductor_antiguos
AFTER UPDATE
ON conductor
FOR EACH ROW
WHEN (OLD.fec_nacimiento < TO_DATE('01/01/1980', 'DD/MM/YYYY'))
BEGIN
INSERT INTO aud_conductor (aud_accion, dni, nombre_old, nombre_new)
VALUES ('UPDATE', :OLD.DNI, :OLD.nombre, :NEW.nombre);
END trg_audit_conductor_antiguos;
/
💡 Truco: Dentro de la cláusula
WHEN, los pseudoregistros se referencian sin los dos puntos:OLD.columnayNEW.columna(no:OLDni:NEW). Los dos puntos solo se usan dentro del bloqueBEGIN...END.
7. INSTEAD OF triggers (sobre vistas)
Los triggers INSTEAD OF permiten hacer modificable una vista que normalmente sería de solo lectura (por ejemplo, vistas con JOINs):
-- Vista que combina conductor y vehículo
CREATE OR REPLACE VIEW vw_conductor_vehiculo AS
SELECT c.DNI, c.nombre, v.matricula, v.marca, v.color
FROM conductor c
JOIN vehiculo v ON c.DNI = v.DNI;
-- Trigger INSTEAD OF para permitir INSERT en la vista
CREATE OR REPLACE TRIGGER trg_iof_conductor_vehiculo
INSTEAD OF INSERT
ON vw_conductor_vehiculo
FOR EACH ROW
BEGIN
-- Insertar en conductor si no existe
MERGE INTO conductor c
USING (SELECT :NEW.DNI AS dni FROM DUAL) src
ON (c.DNI = src.dni)
WHEN NOT MATCHED THEN
INSERT (DNI, nombre, fec_nacimiento, fec_carnet)
VALUES (:NEW.DNI, :NEW.nombre, SYSDATE, SYSDATE);
-- Insertar el vehículo
INSERT INTO vehiculo (matricula, marca, color, DNI)
VALUES (:NEW.matricula, :NEW.marca, :NEW.color, :NEW.DNI);
END trg_iof_conductor_vehiculo;
/
Prueba:
-- Insertar a través de la vista
INSERT INTO vw_conductor_vehiculo (DNI, nombre, matricula, marca, color)
VALUES ('77777777Y', 'Pedro Ruiz', '9999ZZZ', 'Tesla', 'Blanco');
-- Verificar que se insertó en ambas tablas
SELECT * FROM conductor WHERE DNI = '77777777Y';
SELECT * FROM vehiculo WHERE matricula = '9999ZZZ';
📘 Concepto: Los triggers
INSTEAD OFreemplazan la operación original. Oracle no ejecuta el INSERT/UPDATE/DELETE sobre la vista; en su lugar, ejecuta el código del trigger. Solo se pueden crear sobre vistas, no sobre tablas.
8. Triggers DDL y de sistema
8.1. Trigger DDL - Auditar cambios de esquema
-- Tabla para auditar DDL
CREATE TABLE aud_ddl (
aud_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
aud_fecha TIMESTAMP DEFAULT SYSTIMESTAMP,
aud_usuario VARCHAR2(30),
aud_evento VARCHAR2(30),
aud_tipo_obj VARCHAR2(30),
aud_nombre_obj VARCHAR2(128)
);
-- Trigger que audita cualquier operación DDL en el esquema
CREATE OR REPLACE TRIGGER trg_audit_ddl
AFTER DDL
ON SCHEMA
BEGIN
INSERT INTO aud_ddl (aud_usuario, aud_evento, aud_tipo_obj, aud_nombre_obj)
VALUES (
ORA_LOGIN_USER,
ORA_SYSEVENT,
ORA_DICT_OBJ_TYPE,
ORA_DICT_OBJ_NAME
);
END trg_audit_ddl;
/
8.2. Trigger para impedir DROP de tablas
CREATE OR REPLACE TRIGGER trg_proteger_tablas
BEFORE DROP
ON SCHEMA
BEGIN
IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
RAISE_APPLICATION_ERROR(-20050,
'No está permitido eliminar tablas. Tabla: ' || ORA_DICT_OBJ_NAME ||
'. Contacte al DBA.');
END IF;
END trg_proteger_tablas;
/
8.3. Trigger de LOGON
-- Registrar cada inicio de sesión
CREATE OR REPLACE TRIGGER trg_audit_logon
AFTER LOGON
ON SCHEMA
BEGIN
INSERT INTO log_auditoria (accion)
VALUES ('LOGON de ' || USER || ' desde ' || SYS_CONTEXT('USERENV', 'IP_ADDRESS') ||
' a las ' || TO_CHAR(SYSDATE, 'DD/MM/YYYY HH24:MI:SS'));
COMMIT;
END trg_audit_logon;
/
⚠️ Importante: Los triggers de LOGON/LOGOFF requieren el privilegio
ADMINISTER DATABASE TRIGGERsi se crean a nivel de base de datos (ON DATABASEen lugar deON SCHEMA).
9. Triggers compuestos (Compound Triggers)
Los triggers compuestos (disponibles desde Oracle 11g) permiten definir múltiples puntos de disparo en un solo trigger. Son la solución recomendada para evitar el error de tabla mutante (ORA-04091):
CREATE OR REPLACE TRIGGER trg_compound_multa
FOR INSERT OR UPDATE
ON multa
COMPOUND TRIGGER
-- Tipo y colección para almacenar datos entre puntos de disparo
TYPE t_multa_rec IS RECORD (
matricula VARCHAR2(20),
importe NUMBER
);
TYPE t_multas_tab IS TABLE OF t_multa_rec INDEX BY PLS_INTEGER;
v_multas t_multas_tab;
v_idx PLS_INTEGER := 0;
-- BEFORE STATEMENT: inicialización
BEFORE STATEMENT IS
BEGIN
v_multas.DELETE;
v_idx := 0;
END BEFORE STATEMENT;
-- BEFORE EACH ROW: capturar datos de cada fila
BEFORE EACH ROW IS
BEGIN
-- Validar que el importe sea positivo
IF :NEW.importe <= 0 THEN
RAISE_APPLICATION_ERROR(-20020, 'El importe de la multa debe ser positivo');
END IF;
END BEFORE EACH ROW;
-- AFTER EACH ROW: almacenar datos para procesamiento posterior
AFTER EACH ROW IS
BEGIN
v_idx := v_idx + 1;
v_multas(v_idx).matricula := :NEW.matricula;
v_multas(v_idx).importe := :NEW.importe;
END AFTER EACH ROW;
-- AFTER STATEMENT: procesamiento final (puede leer la tabla sin error de mutante)
AFTER STATEMENT IS
v_total_vehiculo NUMBER;
BEGIN
FOR i IN 1..v_multas.COUNT LOOP
SELECT SUM(importe) INTO v_total_vehiculo
FROM multa
WHERE matricula = v_multas(i).matricula;
DBMS_OUTPUT.PUT_LINE(
'Vehículo ' || v_multas(i).matricula ||
': Total multas acumuladas = ' || v_total_vehiculo || ' €');
END LOOP;
END AFTER STATEMENT;
END trg_compound_multa;
/
📘 Concepto: El error de tabla mutante (ORA-04091) ocurre cuando un trigger
FOR EACH ROWintenta leer o consultar la misma tabla sobre la que se disparó. El compound trigger lo resuelve almacenando los datos en la faseAFTER EACH ROWy consultando la tabla en la faseAFTER STATEMENT, donde ya no está en estado de mutación.
10. Gestión de triggers
10.1. Habilitar y deshabilitar
-- Deshabilitar un trigger específico
ALTER TRIGGER trg_horario_laboral DISABLE;
-- Habilitar un trigger específico
ALTER TRIGGER trg_horario_laboral ENABLE;
-- Deshabilitar TODOS los triggers de una tabla
ALTER TABLE conductor DISABLE ALL TRIGGERS;
-- Habilitar TODOS los triggers de una tabla
ALTER TABLE conductor ENABLE ALL TRIGGERS;
10.2. Eliminar triggers
DROP TRIGGER trg_horario_laboral;
DROP TRIGGER trg_audit_conductor_detalle;
10.3. Consultar triggers en el diccionario de datos
-- Ver todos tus triggers
SELECT trigger_name, trigger_type, triggering_event, table_name, status
FROM user_triggers
ORDER BY table_name, trigger_name;
-- Ver el código fuente de un trigger
SELECT trigger_body
FROM user_triggers
WHERE trigger_name = 'TRG_AUDIT_CONDUCTOR_DETALLE';
-- Ver triggers de una tabla específica
SELECT trigger_name, trigger_type, triggering_event, status
FROM user_triggers
WHERE table_name = 'CONDUCTOR'
ORDER BY trigger_name;
-- Ver errores de compilación
SHOW ERRORS TRIGGER trg_audit_conductor_detalle;
-- O bien:
SELECT line, position, text
FROM user_errors
WHERE name = 'TRG_AUDIT_CONDUCTOR_DETALLE'
ORDER BY line;
11. Orden de ejecución de triggers
Cuando hay múltiples triggers sobre una misma tabla y operación, Oracle los ejecuta en este orden:
- BEFORE a nivel de sentencia
- Para cada fila afectada:
- BEFORE a nivel de fila
- Ejecución de la sentencia DML sobre la fila
- AFTER a nivel de fila
- AFTER a nivel de sentencia
Sentencia DML
├── 1. BEFORE STATEMENT trigger(s)
├── Para cada fila:
│ ├── 2. BEFORE EACH ROW trigger(s)
│ ├── 3. Ejecutar DML en la fila
│ └── 4. AFTER EACH ROW trigger(s)
└── 5. AFTER STATEMENT trigger(s)
💡 Truco: Si hay varios triggers del mismo tipo y momento, el orden entre ellos es indeterminado (no se garantiza). Si necesitas un orden específico, usa
FOLLOWS(Oracle 11g+):CREATE OR REPLACE TRIGGER trg_segundo BEFORE INSERT ON conductor FOR EACH ROW FOLLOWS trg_primero BEGIN -- Se ejecuta después de trg_primero NULL; END; /
12. Buenas prácticas
| Práctica | Motivo |
|---|---|
| Triggers cortos y sencillos | Código largo en triggers dificulta el debugging y el mantenimiento |
| No usar COMMIT/ROLLBACK | El trigger forma parte de la transacción que lo disparó |
| Evitar lógica de negocio compleja | Esa lógica pertenece a procedimientos y paquetes |
| No llamar a otros triggers en cadena | Los triggers en cascada son difíciles de depurar (máx. 32 niveles) |
| Usar compound triggers para tabla mutante | Evita ORA-04091 de forma limpia |
Nombrar con prefijo trg_ | Facilita la identificación en el diccionario de datos |
| Documentar el propósito | Un trigger mal documentado se convierte en código misterioso |
| Controlar excepciones | Un error no manejado en un trigger aborta la operación DML completa |
13. Ejercicios prácticos
Ejercicio 1: Auditoría básica de vehículos
Crea una tabla de auditoría y un trigger que registre toda operación INSERT, UPDATE y DELETE sobre la tabla vehiculo:
-- Tabla de auditoría
CREATE TABLE aud_vehiculo (
aud_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
aud_fecha TIMESTAMP DEFAULT SYSTIMESTAMP,
aud_usuario VARCHAR2(30) DEFAULT USER,
aud_accion VARCHAR2(10),
matricula VARCHAR2(20),
marca_old VARCHAR2(50),
marca_new VARCHAR2(50),
color_old VARCHAR2(50),
color_new VARCHAR2(50)
);
-- Trigger
CREATE OR REPLACE TRIGGER trg_audit_vehiculo
AFTER INSERT OR UPDATE OR DELETE
ON vehiculo
FOR EACH ROW
DECLARE
v_accion VARCHAR2(10);
BEGIN
IF INSERTING THEN v_accion := 'INSERT';
ELSIF UPDATING THEN v_accion := 'UPDATE';
ELSE v_accion := 'DELETE';
END IF;
INSERT INTO aud_vehiculo (aud_accion, matricula, marca_old, marca_new, color_old, color_new)
VALUES (v_accion,
COALESCE(:NEW.matricula, :OLD.matricula),
:OLD.marca, :NEW.marca,
:OLD.color, :NEW.color);
END trg_audit_vehiculo;
/
Ejercicio 2: Validación de negocio con trigger BEFORE
Crea un trigger que impida que un conductor tenga el carnet con fecha anterior a su nacimiento o siendo menor de 18 años:
CREATE OR REPLACE TRIGGER trg_validar_conductor
BEFORE INSERT OR UPDATE
ON conductor
FOR EACH ROW
BEGIN
IF :NEW.fec_carnet IS NOT NULL AND :NEW.fec_nacimiento IS NOT NULL THEN
-- El carnet no puede ser anterior al nacimiento
IF :NEW.fec_carnet < :NEW.fec_nacimiento THEN
RAISE_APPLICATION_ERROR(-20030,
'La fecha del carnet no puede ser anterior a la fecha de nacimiento');
END IF;
-- Debe tener al menos 18 años al obtener el carnet
IF MONTHS_BETWEEN(:NEW.fec_carnet, :NEW.fec_nacimiento) / 12 < 18 THEN
RAISE_APPLICATION_ERROR(-20031,
'El conductor debe tener al menos 18 años para obtener el carnet');
END IF;
END IF;
END trg_validar_conductor;
/
-- Prueba (debe fallar)
INSERT INTO conductor (DNI, nombre, fec_nacimiento, fec_carnet)
VALUES ('11111111A', 'MENOR', TO_DATE('01/01/2010', 'DD/MM/YYYY'), SYSDATE);
-- Error: El conductor debe tener al menos 18 años
-- Prueba (debe funcionar)
INSERT INTO conductor (DNI, nombre, fec_nacimiento, fec_carnet)
VALUES ('22222222B', 'MAYOR', TO_DATE('01/01/1990', 'DD/MM/YYYY'), SYSDATE);
Ejercicio 3: Trigger INSTEAD OF sobre vista
Crea una vista que muestre las multas con el nombre del conductor y un trigger que permita insertar multas usando el DNI del conductor:
CREATE OR REPLACE VIEW vw_multas_detalle AS
SELECT m.fecha_multa, m.hora_multa, m.matricula, m.importe,
c.nombre AS conductor, i.descripcion AS infraccion
FROM multa m
JOIN vehiculo v ON m.matricula = v.matricula
JOIN conductor c ON v.DNI = c.DNI
JOIN infraccion i ON m.cod_infraccion = i.cod_infraccion;
CREATE OR REPLACE TRIGGER trg_iof_insertar_multa
INSTEAD OF INSERT
ON vw_multas_detalle
FOR EACH ROW
DECLARE
v_matricula vehiculo.matricula%TYPE;
v_cod_inf infraccion.cod_infraccion%TYPE;
BEGIN
-- Buscar la matrícula por el nombre del conductor
SELECT matricula INTO v_matricula
FROM vehiculo v JOIN conductor c ON v.DNI = c.DNI
WHERE UPPER(c.nombre) LIKE '%' || UPPER(:NEW.conductor) || '%'
AND ROWNUM = 1;
-- Buscar el código de infracción por la descripción
SELECT cod_infraccion INTO v_cod_inf
FROM infraccion
WHERE UPPER(descripcion) LIKE '%' || UPPER(:NEW.infraccion) || '%'
AND ROWNUM = 1;
-- Insertar la multa
INSERT INTO multa (fecha_multa, hora_multa, matricula, cod_infraccion, importe)
VALUES (:NEW.fecha_multa, :NEW.hora_multa, v_matricula, v_cod_inf, :NEW.importe);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20040, 'No se encontró conductor o infracción');
END trg_iof_insertar_multa;
/
Ejercicio 4: Compound trigger para evitar tabla mutante
Crea un compound trigger que valide que el total de multas de un vehículo no supere los 10.000 €:
CREATE OR REPLACE TRIGGER trg_limite_multas_vehiculo
FOR INSERT OR UPDATE OF importe
ON multa
COMPOUND TRIGGER
TYPE t_matriculas IS TABLE OF multa.matricula%TYPE INDEX BY PLS_INTEGER;
v_matriculas t_matriculas;
v_idx PLS_INTEGER := 0;
AFTER EACH ROW IS
BEGIN
v_idx := v_idx + 1;
v_matriculas(v_idx) := :NEW.matricula;
END AFTER EACH ROW;
AFTER STATEMENT IS
v_total NUMBER;
BEGIN
FOR i IN 1..v_matriculas.COUNT LOOP
SELECT NVL(SUM(importe), 0) INTO v_total
FROM multa
WHERE matricula = v_matriculas(i);
IF v_total > 10000 THEN
RAISE_APPLICATION_ERROR(-20060,
'El vehículo ' || v_matriculas(i) ||
' supera el límite de 10.000 € en multas (Total: ' || v_total || ' €)');
END IF;
END LOOP;
END AFTER STATEMENT;
END trg_limite_multas_vehiculo;
/
Resumen
| Concepto | Detalle |
|---|---|
| Trigger | Bloque PL/SQL que se ejecuta automáticamente ante un evento |
| BEFORE / AFTER | Momento de disparo: antes o después de la operación |
| FOR EACH ROW | Trigger a nivel de fila (se ejecuta por cada fila afectada) |
| Nivel de sentencia | Sin FOR EACH ROW; se ejecuta una vez por sentencia |
| :NEW | Pseudoregistro con los valores nuevos (INSERT/UPDATE) |
| :OLD | Pseudoregistro con los valores anteriores (UPDATE/DELETE) |
| INSERTING / UPDATING / DELETING | Predicados para saber qué operación disparó el trigger |
| UPDATE OF columna | Dispara solo cuando se modifica una columna específica |
| WHEN (condición) | Condición adicional para el disparo (sin : en OLD/NEW) |
| INSTEAD OF | Trigger sobre vistas; reemplaza la operación original |
| Trigger DDL | Disparado por CREATE, ALTER, DROP en el esquema |
| Trigger LOGON/LOGOFF | Disparado al conectarse/desconectarse de la BD |
| Compound trigger | Trigger con múltiples puntos de disparo; resuelve tabla mutante |
| FOLLOWS | Establece orden entre triggers del mismo tipo |
| ALTER TRIGGER … DISABLE/ENABLE | Habilita o deshabilita un trigger |
| USER_TRIGGERS | Vista del diccionario para consultar triggers |
| Tabla mutante (ORA-04091) | Error al consultar la tabla desde un trigger FOR EACH ROW |