Este minitutorial tiene como objetivo captar todos los conceptos dictados en cada sesión en el curso de Base de Datos Avanzado II, así como brindar apoyo a los alumnos de la carrera técnica de Computación e Informática, que por algún motivo no hayan asistido a clases.
UNIDAD 5. Programación Avanzada en ORACLE
Logro de la Unidad de Aprendizaje
Al término de la unidad, el alumno construye programas avanzados utilizando el lenguaje PL/SQL que soporten la lógica de negocio mediante la incorporación de funciones y procedimientos almacenados organizados en paquetes y a través de disparadores almacenados en la base de datos.
Temario
5.1. Tema 5: Programación dentro de una base de datos Oracle
5.1.1. Construcción de procedimientos y funciones almacenados.
5.1.2. Construcción de paquetes almacenados
5.1.3. Construcción de disparadores
1. /*
Sesión12 – Disparadores
Estudiante: José Luis Toro Alcarraz
Curso: Base de Datos Avanzado II
Correo:i201010865@cibertec.edu.pe
*/
Objetivos de la sesión.
Definir qué es un disparador y su uso.
Entender las diferentes modalidades.
Definir y aplicar disparadores que resuelvan problemas y/o requerimientos que se dan en
la vida real.
1) Creación de disparadores
2) Componentes de un disparador
1) Creación de disparadores
a) Definición de un disparador
Un disparador (trigger) son bloques nominados de PL/SQL.
Deben estar almacenados en la base de datos como objetos independientes y no pueden
ser locales a un bloque o paquete.
A diferencia de los procedimientos y funciones que son llamados explícitamente, un
disparador se ejecuta de forma implícita cuando ocurre el suceso que lo activa.
No acepta parámetros.
Ayudan a mantener restricciones de integridad complejas que no pueden hacerse a través
de restricciones declarativas específicas durante la creación de la tabla.
Permiten auditar la información contenida en la tabla, registrando las modificaciones y el
autor de las mismas.
Pueden avisar automáticamente a otros módulos de PL/SQL de que hay que llevar a cabo
una determinada acción, cuando se realiza un cambio en una tabla.
b) Tipos de disparadores
Disparadores DML: Se activan a través de una instrucción DML y el tipo de instrucción
determina el tipo de disparador. Pueden definirse para las sentencias INSERT, UPDATE o
DELETE y pueden dispararse antes o después de la sentencia.
Disparadores de sustitución: Pueden definirse únicamente sobre vistas. A diferencia de
los disparadores DML que se ejecutan además de la sentencia DML, un disparador de
sustitución se ejecuta en lugar de la instrucción DML que lo activa.
Disparadores del sistema: Se activan cuando tiene lugar un suceso del sistema, como la
conexión o desconexión de la base de datos. También puede dispararse con instrucciones
DDL, como la creación de tablas.
2. c) Creación de un disparador DML
nombre_disparador: Es el nombre del disparador que se quiere crear.
tabla_referencia: Es la tabla para la cual se define el disparador.
cuerpo_disparador: Es un bloque PL/SQL que constituye el código del disparador.
condición: Es la condición que se evalúa antes de dispararse.
SINTAXIS:
CREATE [OR REPLACE] TRIGGER nombre_disparador
{BEFORE|AFTER}
{DELETE|INSERT|UPDATE [OF col1, col2, . ., colN]} ON tabla_referencia
[REFERENCING OLD AS oldname, NEW as newname]old
[FOR EACH ROW [WHEN (condición)]]
Ejemplo:
-- Creamos la tabla log_dept
CREATE TABLE LOG_DEPT
(DEPTNO NUMBER(4),DNAME VARCHAR2(20),LOCAL VARCHAR2(20),USUARIO
VARCHAR2(20),FECHAHORA DATE);
Tabla creada.
-- Creamos el trigger
CREATE OR REPLACE TRIGGER LOG_DEPT_INS
AFTER INSERT ON DEPT
FOR EACH ROW
BEGIN
-- Insertamos los valores nuevos
INSERT INTO LOG_DEPT
VALUES (:NEW.DEPTNO, :NEW.DNAME, :NEW.LOC, USER, SYSDATE);
DBMS_OUTPUT.PUT_LINE('Insertamos el nuevo registro.');
END;
/
Disparador creado.
-- Comprobamos
INSERT INTO DEPT(DEPTNO,DNAME,LOC)
VALUES(50,'RRHH','Surco');
Insertamos el nuevo registro.
1 fila creada.
SELECT * FROM LOG_DEPT;
DEPTNO DNAME LOCAL
---------- ---------- --------50 RRHH
Surco
USUARIO
------------SCOTT
FECHAHOR
-----------------03/04/12
3. 2) Componentes de un disparador
Mientras que un procedimiento no puede tener el mismo nombre que un paquete, una tabla
o una función, un disparador si puede tenerlo ya que el espacio para sus nombres es
diferente al de los otros objetos de la base de datos que usan el mismo espacio.
a) Tipos de disparadores
El suceso de disparo determina el tipo de disparador. Los disparadores pueden definirse
para las sentencias INSERT, UPDATE o DELETE y pueden dispararse antes o después de
la operación.
Además se puede decir que el nivel de los disparadores puede ser por fila o sentencia.
Categoría
Valores
Orden
INSERT, DELETE Y UPDATE
Temporización
BEFORE O AFTER
Nivel
FILA O SENTENCIA
Comentarios
Define que tipo de orden DML provoca la
activación del disparador.
Define si el disparador se activa antes o
después de que se ejecute la orden
(Disparador previo o posterior).
Los disparadores con nivel de fila (FOR
EACH ROW) se activan una vez por cada fila
afectada por la orden que provoco el disparo.
Los
disparadores
con
nivel
de
orden(STATEMENT) se activa solo una vez,
antes o después de la sentencia que activa el
disparo
b) Tabla mutante
Una tabla mutante es una tabla que está modificándose actualmente por una orden DML.
Para un disparador, ésta es la tabla sobre la que está definido.
Las órdenes SQL en el cuerpo de un disparador no pueden leer o modificar ninguna tabla
mutante de la orden que provoca el disparo. Esto incluye a la propia tabla del disparador.
Ejemplo:
-- Trigger que actualiza una orden
CREATE OR REPLACE TRIGGER ACTUALIZA_ORDEN
AFTER INSERT OR DELETE OR UPDATE ON ITEM
FOR EACH ROW
DECLARE
V_TOTAL NUMBER(6);
BEGIN
-- Obtenemos el nuevo total de la orden
SELECT SUM(ITEMTOT) INTO V_TOTAL
FROM ITEM
4. WHERE ORDID = :NEW.ORDID;
-- Actualizamos el monto total de la orden
UPDATE ORD
SET TOTAL = V_TOTAL
WHERE ORDID = :NEW.ORDID;
END;
/
-- Comprobar
UPDATE ITEM
SET ITEMTOT = CANTIDAD * ACTUALPRICE;
c) Restricciones
Un disparador no puede emitir ninguna orden de control de transacciones: COMMIT,
ROLLBACK o SAVEPOINT.
En base a la restricción anterior, se concluye también que ningún procedimiento o función
que es llamado desde un disparador, puede emitir una orden de control de transacciones.
El cuerpo del disparador no puede contener ninguna declaración de variables LONG o
LONG RAW.
No puede leer ni modificar tablas mutantes.
d) Seudoregistros
Cuando un disparador es disparado, puede accederse a la fila que está siendo
actualmente procesada utilizando, para ello, dos seudoregistros, :old y :new.
Orden de disparo
INSERT
UPDATE
DELETE
:old
No definido: todos los campos
toman el valor NULL.
Valores originales de la fila,
antes de la actualización
Valores originales, antes del
borrado de la fila
:new
Valores que serán insertados
cuando se complete la orden.
Nuevos valores que serán
escritos cuando se complete la
orden.
No definido: todos los campos
toman el valor NULL
Ejemplo1:
-- Limpiamos la tabla log_dept
DELETE FROM LOG_DEPT;
1 fila suprimida.
-- Creamos el trigger
CREATE OR REPLACE TRIGGER LOG_DEPT
AFTER UPDATE ON DEPT
FOR EACH ROW
BEGIN
-- Insertamos los valores anterioriores
INSERT INTO LOG_DEPT
VALUES (:OLD.DEPTNO, :OLD.DNAME, :OLD.LOC, USER, SYSDATE);
5. -- Insertamos los valores nuevos
INSERT INTO LOG_DEPT
VALUES (:NEW.DEPTNO, :NEW.DNAME, :NEW.LOC, USER, SYSDATE);
END;
/
Disparador creado.
-- Comprobamos
UPDATE DEPT
SET DNAME='CONTABILIDAD', LOC='SAN LUIS'
WHERE DEPTNO =50;
1 fila actualizada.
SELECT * FROM LOG_DEPT;
DEPTNO DNAME
------------- -------------------50 RRHH
50 CONTABILIDAD
LOCAL
USUARIO FECHAHOR
--------------- --------------- ----------------Surco
SCOTT
03/04/12
SAN LUIS SCOTT
03/04/12
Ejemplo2:
CREATE OR REPLACE TRIGGER ACTUALIZA_ORD
AFTER INSERT ON ITEM
FOR EACH ROW
BEGIN
-- Actualizamos orden
UPDATE ORD
SET TOTAL = TOTAL + :NEW.ITEMTOT
WHERE ORDID = :NEW.ORDID;
END;
/
e) Cláusula WHEN
La cláusula WHEN sólo es válida para 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.
Donde condición es una expresión booleana que será evaluada para cada fila. Se puede
hacer referencia a las variables :new y :old dentro de la condición, pero no se utilizan los
dos puntos ( : )
WHEN condicion
Ejemplo:
-- Limpiamos la tabla log_dept
6. DELETE FROM LOG_DEPT;
2 filas suprimidas.
-- Creamos el trigger
CREATE OR REPLACE TRIGGER LOG_DEPT
AFTER UPDATE ON DEPT
FOR EACH ROW
WHEN (OLD.DNAME <> NEW.DNAME OR OLD.LOC <> NEW.LOC)
BEGIN
-- Insertamos los valores anteriores
INSERT INTO LOG_DEPT
VALUES (:OLD.DEPTNO, :OLD.DNAME, :OLD.LOC, USER, SYSDATE);
-- Insertamos los valores nuevos
INSERT INTO LOG_DEPT
VALUES (:NEW.DEPTNO, :NEW.DNAME, :NEW.LOC, USER, SYSDATE);
END;
/
Disparador creado.
-- Comprobamos
UPDATE DEPT
SET DNAME='EJECUTIVO', LOC='SAN MIGUEL'
WHERE DEPTNO =50;
1 fila actualizada.
SELECT * FROM LOG_DEPT;
DEPTNO DNAME
---------- -------------------50 CONTABILIDAD
50 EJECUTIVO
LOCAL
USUARIO FECHAHOR
-------------------- -------------- ------------------SAN LUIS
SCOTT
03/04/12
SAN MIGUEL SCOTT
03/04/12
f) Utilización de predicados
Un sólo disparador se puede ejecutar por una inserción, por una actualización o por una
eliminación de una fila de una tabla. El problema radica en diferenciar, dentro del cuerpo
del disparador, cuál fue el tipo acción que ejecutó el disparador. Para esto podemos hacer
uso de los predicados que devuelven true o false según la sentencia que se ejecutó.
Predicado
INSERTING
UPDATING
DELETING
Ejemplo:
Comportamiento
TRUE si la orden de disparo es INSERT; FALSE en caso contrario
TRUE si la orden de disparo es UPDATING; FALSE en caso contrario
TRUE si la orden de disparo es DELETE; FALSE en caso contrario
7. -- Limpiamos la tabla log_dept
DELETE FROM LOG_DEPT;
2 filas suprimidas.
-- Creamos el trigger
CREATE OR REPLACE TRIGGER LOG_DEPT
AFTER INSERT OR UPDATE OR DELETE ON DEPT
FOR EACH ROW
WHEN (OLD.DNAME <> NEW.DNAME OR OLD.LOC <> NEW.LOC)
BEGIN
-- Insertamos los valores anteriores
IF DELETING OR UPDATING THEN
INSERT INTO LOG_DEPT
VALUES (:OLD.DEPTNO, :OLD.DNAME, :OLD.LOC, USER, SYSDATE);
END IF;
-- Insertamos los valores nuevos
IF INSERTING OR UPDATING THEN
INSERT INTO LOG_DEPT
VALUES (:NEW.DEPTNO, :NEW.DNAME, :NEW.LOC, USER, SYSDATE);
END IF;
END;
/
Disparador creado.
-- Comprobamos
UPDATE DEPT
SET DNAME='EJECUTIVO', LOC='SAN MIGUEL'
WHERE DEPTNO =50;
1 fila actualizada.
SELECT * FROM LOG_DEPT;
DEPTNO DNAME
LOCAL
USUARIO FECHAHOR
------------- --------------------- ------------------ -------------- -------50 PRESIDENCIA LIMA
SCOTT
08/04/12
50
EJECUTIVO
SAN MIGUEL SCOTT
08/04/12
Problema1: Verificamos el nivel de los triggers, para ello ejecutamos los siguientes triggers.
-- Ejecutamos los siguientes triggers
CREATE OR REPLACE TRIGGER TR_BEFORE_STATEMENT
BEFORE UPDATE ON EMP
BEGIN
-- INSERTAMOS LOS VALORES NUEVOS
DBMS_OUTPUT.PUT_LINE('EJECUTANDO TRIGGER BEFORE FOR STATEMENT.');
8. END;
/
CREATE OR REPLACE TRIGGER TR_BEFORE_FOREACHROW
BEFORE UPDATE ON EMP
FOR EACH ROW
BEGIN
-- INSERTAMOS LOS VALORES NUEVOS
DBMS_OUTPUT.PUT_LINE('EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: '
|| :NEW.EMPNO);
END;
/
CREATE OR REPLACE TRIGGER TR_AFTER_STATEMENT
AFTER UPDATE ON EMP
BEGIN
-- INSERTAMOS LOS VALORES NUEVOS
DBMS_OUTPUT.PUT_LINE('EJECUTANDO TRIGGER AFTER FOR STATEMENT.');
END;
/
CREATE OR REPLACE TRIGGER TR_AFTER_FOREACHROW
AFTER UPDATE ON EMP
FOR EACH ROW
BEGIN
-- INSERTAMOS LOS VALORES NUEVOS
DBMS_OUTPUT.PUT_LINE('EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: ' ||
:NEW.EMPNO);
END;
/
-- Comprobamos
UPDATE EMP
SET COMM = COMM * 100;
EJECUTANDO TRIGGER BEFORE FOR STATEMENT.
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7499
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7499
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7521
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7521
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7654
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7654
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7698
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7698
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7782
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7782
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7839
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7839
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7844
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7844
9. EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7499
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7499
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7521
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7521
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7654
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7654
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7698
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7698
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7782
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7782
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7839
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7839
EJECUTANDO TRIGGER BEFORE FOR EACH ROW. EMPNO: 7844
EJECUTANDO TRIGGER AFTER FOR EACH ROW. EMPNO: 7844
EJECUTANDO TRIGGER AFTER FOR STATEMENT.
14 filas actualizadas.
-- Eliminamos los triggers.
DROP TRIGGER TR_BEFORE_STATEMENT;
DROP TRIGGER TR_BEFORE_FOREACHROW;
DROP TRIGGER TR_AFTER_STATEMENT;
DROP TRIGGER TR_AFTER_FOREACHROW;
Problema2: Trigger que verifique que a ningún empleado se le aumente el salario en más de 20%.
-- Trigger que valida saldo
CREATE OR REPLACE TRIGGER TR_VALIDA_SALDO
BEFORE UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE('Ejecutando trigger');
IF :NEW.SAL > :OLD.SAL * 1.2 THEN
-- Falla el primero y fallo todas
RAISE_APPLICATION_ERROR(-20001,'Salario no puede ser mayor a 20%');
END IF;
END;
/
Disparador creado.
-- Comprobamos
UPDATE EMP
SET SAL = SAL + 900;
UPDATE EMP
*
ERROR en lÝnea 1:
ORA-20001: Salario no puede ser mayor a 20%
ORA-06512: en "SCOTT.TR_VALIDA_SALDO", lÝnea 5
10. ORA-04088: error durante la ejecuci¾n del disparador 'SCOTT.TR_VALIDA_SALDO'
Otra manera:
-- Trigger que valida saldo
CREATE OR REPLACE TRIGGER TR_VALIDA_SALDO
BEFORE UPDATE OF SAL ON EMP
FOR EACH ROW WHEN (NEW.SAL > OLD.SAL * 1.2)
BEGIN
DBMS_OUTPUT.PUT_LINE('EJECUTANDO TRIGGER');
-- Falla el primero y fallo todas
RAISE_APPLICATION_ERROR(-20001, ‘Salario no puede ser mayor a 20%');
END;
/
-- Comprobamos
UPDATE EMP
SET SAL = SAL + 900;
UPDATE EMP
*
ERROR en lÝnea 1:
ORA-20001: Salario no puede ser mayor a 20%
ORA-06512: en "SCOTT.TR_VALIDA_SALDO", lÝnea 5
ORA-04088: error durante la ejecuci¾n del disparador 'SCOTT.TR_VALIDA_SALDO'
Nota1: En las validaciones se tiene que forzar el error. (RAISE_APPLICATION_ER.ROR).
Nota2: En el la clausula WHEN los pseudos registros no llevan dos puntos.