jueves, 25 de febrero de 2010

¿Que son tablas mutantes?

Pues una tabla mutante o que esta mutando, es aquella que esta siendo modificada, ya sea por un INSERT, un DELETE o un UPDATE o una tabla que podría modificarse por los efectos de un ON DELETE CASCADE.

Por otro lado en este tipo de tablas hay dos tipos de restricciones, las cuales se presentan a continuación:

1. Las instrucciones de un row trigger no pueden ni leer ni modificar una tabla que está mutando.

2. Las instrucciones de un row trigger no pueden cambiar ni la clave primaria, ni claves foráneas, ni atributos únicos de tablas que estén restringiendo. Esta restricción tiene una excepción: un before row trigger disparado por un INSERT de una sola fila de una tabla con una clave foránea, puede modificar cualquier columna de la tabla primaria siempre que no se viole ninguna de las restricciones de integridad referencial.

Los ERRORES por Tablas Mutantes se detectan y se generan en Tiempo de Ejecución y no de Compilación (ORA-4091).


Ejemplo de Tablas Mutantes

A continuación la descripción de el disparador que da lugar a las tablas mutantes.

Este será un disparador que modifique el número de empleados de un departamento (columna Departamentos.Num_Emp) cada vez que sea necesario. Ese número cambia al INSERTAR o BORRAR uno o más empleados, y al MODIFICAR la columna Dpto de la tabla Empleados, para uno o varios empleados. La tabla Departamentos es una tabla de restricción de la tabla Empleados, pero el Disparador es correcto, porque modifica Num_Emp, que no es la clave primaria. Este disparador no puede consultar la tabla Empleados, ya que esa tabla es mutante (por ejemplo, esta instrucción no sería válida: SELECT COUNT(*) INTO T FROM Empleados WHERE Dpto = :new.Dpto;)

CREATE OR REPLACE TRIGGER Cuenta_Empleados
BEFORE DELETE OR INSERT OR UPDATE OF Dpto ON Empleados
FOR EACH ROW
BEGIN
IF INSERTING THEN
UPDATE Departamentos SET Num_Emp = Num_Emp+1
WHERE NumDpto=:new.Dpto;
ELSIF UPDATING THEN
UPDATE Departamentos SET Num_Emp = Num_Emp+1
WHERE NumDpto=:new.Dpto;
UPDATE Departamentos SET Num_Emp = Num_Emp-1
WHERE NumDpto=:old.Dpto;
ELSE
UPDATE Departamentos SET Num_Emp = Num_Emp-1
WHERE NumDpto=:old.Dpto;
END IF;
END;

El disparador anterior Cuenta_Empleados tiene un inconvenientes: modifica el campo Num_Emp aunque este no tenga el valor correcto.


Ejemplo de la solución para las Tablas Mutantes


Ahora bien basándonos en el ejemplo anterior de tablas mutantes, se escribirá la solución basándonos en la creación de dos nuevos disparadores, uno a nivel de fila y el otro a nivel de after, a continuación se escribira:


CREATE OR REPLACE PACKAGE Empleados_Dpto AS
TYPE T_Dptos IS TABLE OF Empleados.Dpto%TYPE
INDEX BY BINARY_INTEGER;
Tabla_Dptos T_Dptos;
END Empleados_Dpto;
CREATE OR REPLACE TRIGGER Fila_Cuenta_Empleados
AFTER DELETE OR INSERT OR UPDATE OF Dpto ON Empleados FOR EACH ROW
DECLARE Indice BINARY_INTEGER;
BEGIN
Indice := Empleados_Dpto.Tabla_Dptos.COUNT + 1;
IF INSERTING THEN
Empleados_Dpto.Tabla_Dptos(Indice) := :new.Dpto;
ELSIF UPDATING THEN
Empleados_Dpto.Tabla_Dptos(Indice) := :new.Dpto;
Empleados_Dpto.Tabla_Dptos(Indice+1):= :old.Dpto;
ELSE Empleados_Dpto.Tabla_Dptos(Indice) := :old.Dpto;
END IF;
END Fila_Cuenta_Empleados;
CREATE OR REPLACE TRIGGER Orden_Cuenta_Empleados
AFTER DELETE OR INSERT OR UPDATE OF Dpto ON Empleados
DECLARE Indice BINARY_INTEGER;
Total Departamentos.Num_Emp%TYPE;
Departamento Departamentos.NumDpto%TYPE;
BEGIN
FOR Indice IN 1..Empleados_Dpto.Tabla_Dptos.COUNT LOOP
Departamento := Empleados_Dpto.Tabla_Dptos(Indice);
SELECT COUNT(*) INTO Total FROM Empleados WHERE Dpto = Departamento;
UPDATE Departamentos SET Num_Emp = Total WHERE NumDpto = Departamento;
END LOOP;
Empleados_Dpto.Tabla_Dptos.DELETE;
END Orden_Cuenta_Empleados;


Exitos!!!!