La transformación de restricciones de integridad semánticas abordadas en este trabajo se ha llevado a cabo en el marco de BD relacionales y especificación de disparadores en SQL3. Aunque todos los sistemas activos usan distintas sintaxis de disparadores y distintos modelos de ejecución, la transformación de una restricción de integridad a una regla activa sigue algunas reglas comunes independientes de las características del sistema comercial [Türker y Gertz, 2000].
1. Detección de las operaciones críticas que afecten las restricciones de integridad
Para una restricción de integridad es importante detectar primero las operaciones críticas (INSERT, DELETE, o UPDATE) que pueden afectar esta restricción, es decir, las actualizaciones que pueden producir una violación de una restricción de integridad. La importancia de esta fase viene motivada por saber exactamente ¿Cuáles son las operaciones que pueden violar una restricción determinada? Cuando una operación alerta a una restricción esto significa que es necesario implementar un mecanismo para controlar esa restricción.
Para mostrar cómo se detectan las operaciones críticas primeramente se presentarán varios ejemplos para a continuación mostrar las reglas.
RI1: “La experiencia en años de un director debe ser mayor que la de sus empleados”:
∀ d ∈ Empleado, e ∈ Empleado:
d.tipo = ‘dir’ ∧ e.tipo = ‘emp’
⇒ d.año > e.año
• DELETE: El borrado un director o un empleado no alerta RI1.
• INSERT: La inserción de un director o de un empleado si puede violar RI1 porque se deben verificar que los valores nuevos de los atributos que aparecen (año o tipo) en la tupla insertada coinciden con la especificación de RI1.
• UPDATE: Se puede considerar que una actualización es una operación de borrado de los valores antiguos y una operación de inserción de nuevos valores, y por ello, una operación de actualización puede violar cualquiera de los atributos que aparecen en RI1 (año y tipo).
RI2: “Cada departamento gestiona al menos un proyecto”:
∀ d ∈ Departamento:
• DELETE: El borrado de un proyecto puede violar RI2 porque puede suceder que se borre el último proyecto relacionado con un departamento.
• INSERT: La inserción de un departamento nuevo puede violar RI2 porque cada departamento tiene que relacionarse con al menos un proyecto.
• UPDATE: La actualización de la columna (deptno) en la tabla proyecto puede violar RI2 porque puede ocurrir que se actualice el último proyecto relacionado con un departamento.
RI3: “El sueldo de un empleado puede aumentar como máximo en un 5%”:
∀ e ∈ Empleado, e’ ∈ Empleado:
e.id = e’.id ∧ e’.tipo = ‘emp’
⇒ e.sueldo ≤ e’.sueldo*1.05
• DELETE: El borrado de un director no viola RI3. • INSERT: La inserción de un director no viola RI3.
• UPDATE: La actualización de la columna (sueldo) puede violar RI3 donde se debe comprobar que el aumento no supera al 5%.
RI4: “El sueldo total de los empleados que trabajan en un departamento no debe exceder el presupuesto del departamento”:
∀ d ∈ Departamento:
d.total≥SUM{{e.sueldo| e∈Empleado ∧ e.deptno=d.id}}
• DELETE: El borrado de un empleado no viola RI4.
• INSERT: La inserción de un empleado puede violar la RI4 donde se debe controlar que la suma total de los sueldos de los empleados que pertenecen al departamento no supera el presupuesto del mismo.
• UPDATE: La actualización de la columna (total) de un departamento puede afectar a la RI4, así como la actualización de uno o más sueldos de los empleados.
Según los resultados obtenidos en [Türker y Gertz, 2000] se han derivado y refinado un conjunto de reglas para detectar las operaciones críticas a tener en cuenta cuando se realice la transformación de cláusulas relacionales a disparadores. A continuación se presentan las reglas:
1. Las restricciones que se especifican por variables cuantificadas universalmente (∀), tendrán como operaciones críticas las inserciones en la tabla asociada a estas variables. Ejemplos de estas reglas son RI1 (Empleado), RI2 (Departamento), RI4 (Departamento).
2. En las restricciones que se especifican mediante variables cuantificadas existencialmente (∃), las operaciones críticas serán los borrados de la tabla asociada a estas variable, por ejemplo RI2 (Proyecto).
3. En ambos casos (a) y (b), las operaciones de actualización de las columnas utilizadas en las comparaciones también serán críticas. Por ejemplo, RI1 (Empleado.sueldo, Empleado.tipo), RI2 (Proyecto.deptno), RI4 (Departamento.total).
4. En el caso de haber una comparación entre valores de variables o tablas de transición vistas en la sección 2.3.1.2, las operaciones de actualización de las columnas asociadas son críticas.
Como por ejemplo ocurre en RI3 (Empleado.sueldo).
2. Determinar la granularidad del disparador
Como se explica en el capítulo 2, la granularidad de transición indica la interrelación entre la ocurrencia de un evento y la instanciación de la regla que ha sido disparada. Existen dos tipos de granularidad: orientada a tupla y orientada a conjunto. En esta sección se describe las reglas generales que se pueden utilizar para definir la granularidad del disparador que se va a crear para cada operación crítica. Es decir, ver si la comprobación de cada restricción de integridad se realiza para cada tupla individual o para todas las tuplas afectadas por esa operación. Si la operación afecta a solo una tupla, entonces se utiliza un disparador de granularidad orientada a tupla,
mientras que si la operación afecta al conjunto de las tuplas afectadas se utilizará un disparador de granularidad orientada a conjunto.
Las reglas que se han utilizado para determinar las granularidades de los disparadores son:
(a) Toda restricción puede verificarse por un disparador orientado a conjunto. Según la propuesta de [Türker y Gertz, 2000] por razones de rendimiento se considera que los disparadores orientados a tupla son preferibles porque permiten verificar condiciones solamente en las tuplas actualizadas.
Por ejemplo, en la restricción de integridad RI1 se pueden utilizar los dos tipos de disparadores pero, en el caso de utilizar un disparador orientado a tupla, ese disparador debe verificar RI1 solamente cuando se inserta una tupla nueva en la tabla Empleado con independencia del tamaño de la tabla. Si se utiliza un disparador orientado a conjunto, el disparador verificará todas las tuplas que puede haber en la tabla y esto es computacionalmente muy costoso.
(b) Las restricciones de integridad que incluyen funciones agregadas requieren siempre por lo menos un disparador orientado a conjunto para la tabla y las tuplas especificadas en la agregación.
Por ejemplo, para RI4 se requiere un disparador orientado a conjunto para las operaciones insertar un empleado y modificar el sueldo de un empleado.
(c) Las restricciones con condiciones generales complejas, según lo presentado en la sección 3.1.1, pueden ser verificadas solamente utilizando disparadores orientados a tupla ya que son los únicos que en el estándar SQL3 utilizan la cláusula WHEN.
(d) Toda restricción que compare valores que ya están en la BD con los valores nuevos que se van a actualizar serán verificadas a través de disparadores orientados a la tupla debido a que son los únicos disparadores en el estándar SQL3 que permiten utilizar variables de transición (NEW, OLD). Por ejemplo, RI3 se transforma a un disparador orientado a tupla.
3. Especificar el tiempo de activación del disparador
Aunque en el estándar SQL se permiten los dos tipos de tiempo de activación (BEFORE y AFTER) sin ningún tipo de restricción, la mayoría de los sistemas comerciales tienen algunas restricciones sobre el tiempo de activación. Por ejemplo, los sistemas ORACLE 9i y DB2 permiten activar dos tipos de disparadores según el tiempo de activación (BEFORE/AFTER), y el sistema MS-SQL SERVER 2005 permite solo los disparadores de tipo AFTER. El estándar SQL3 recomienda que los disparadores de tipo BEFORE se utilicen para leer de la BD, y los de tipo tupla (FOR EACH ROW) sean los que utilicen las sentencias de asignación para modificar los valores introducidos [Cochrane et al., 1996]. Por ejemplo, “Asegúrese que el sueldo nunca sea mayor de 5000 €”, en este caso, se puede emplear un disparador de inserción de tipo BEFORE-FOR EACH ROW para garantizar que el Sueldo = 5000, si el nuevo valor es mayor de 5000.
Por ello, las reglas que se van a utilizar en nuestro trabajo para especificar el tiempo de activación de cada disparador son las siguientes:
(a) Cuando se tenga en la cláusula TRC solo la variable de transición NEW en la condición, entonces se utilizará un tiempo de activación BEFORE, ya que no es necesario comparar con otros valores que ya están en la BD.
(b) En los demás casos se utilizará un tiempo de activación AFTER, como por ejemplo sucede en las restricciones RI1, RI2, RI3, y RI4.
4. Convertir las cláusulas del cálculo relacional a condiciones de SQL3
Una vez establecidas las operaciones críticas y las granularidades de los disparadores, el siguiente paso es convertir las cláusulas de TRC a condiciones expresadas en SQL3. Estas condiciones serán la negación lógica de la restricción de integridad. Por ejemplo, si la restricción indica que en la base de datos sólo pueden existir empleados mayores de 18 años, la condición para que se active un disparador será cuando esta restricción no se cumpla, es decir, cuando la edad del empleado sea menor que 18.
A continuación se presentan cómo se aplica este paso a los ejemplos estudiados anteriormente.
¬RI1: “La experiencia en años de un director debe ser menor o igual que la de sus empleados”:
≡ ∀ d ∈ Empleado, e ∈ Empleado:
d.tipo = ‘dir’ ∧ e.tipo = ‘emp’
⇒ d.año ≤ e.año
≡ EXISTS
(SELECT * FROM Empleado e1, Empleado e2
WHERE e1.tipo= ‘dir’ AND e2.tipo= ‘emp’ AND e1.año =< e2.año);
¬RI2: “Existen departamentos que no gestionan proyectos”:
≡ ∀ d ∈ Departamento:
¬ ∃ p ∈ Proyecto: d.id = p.deptno
≡ EXISTS
(SELECT * FROM DEPARTAMENTO WHERE id NOT IN (SELECT deptno FROM PROYECTO))
¬RI3: “El sueldo de un empleado puede aumentar en más de un 5%”:
≡ ∀ e ∈ Empleado, e’ ∈ Empleado:
e.id = e’.id ∧ e’.tipo = ‘emp’
⇒ e.sueldo > e’.sueldo*1.05
≡ EXISTS
(SELECT * FROM Empleado
WHERE NEW.id = OLD.id AND OLD.tipo = ‘emp’ AND NEW.sueldo > OLD.sueldo*1.05);
¬RI4: “El sueldo total de los empleados que trabajan en un departamento puede exceder el presupuesto del departamento”:
≡ ∀ d ∈ Departamento:
d.total < SUM{{e.sueldo| e ∈ Empleado ∧
e.deptno = d.id}}
≡ EXISTS
(SELECT * FROM Empleado e, Departamento d GROUP BY e.deptno
HAVING d.total < SUM(e.sueldo));
Así pues, para convertir las cláusulas TRC a condiciones del SQL3 se tendrá que utilizar la siguiente sintaxis:
EXISTS (SELECT Columnas FROM Tablas WHERE Condición);
A continuación, pasamos a determinar las incógnitas de esta sintaxis como son las columnas, las tablas y las condiciones dependiendo de la cláusula TRC.
Columnas:
• Las columnas son los atributos que aparecen en la cláusula TRC, en la mayoría de los casos se ha utilizado el asterisco (*) porque lo más importante es saber si la consulta devuelve resultado o no; si la consulta devuelve cualquier resultado significa que existe una violación de la restricción y hay que controlar esa violación.
Tablas:
• Todas las tablas que aparecen en la cláusula TRC aparecen en la cláusula FROM. • En el caso de que aparezca la misma tabla más de una vez con distintas variables, se utilizarán tantos alias como variables. La restricción RI1 muestra esta situación (FROM Empleado d, Empleado e).
Condición:
• En la condición se especifican todas las formulas (atoms) introducidas en la cláusula TRC.
• En el caso de tener una restricción condicional con el símbolo de implicación (⇒), este símbolo se transforma al operador AND. Un ejemplo se muestra en RI1 y RI3.
• En el caso de tener variables cuantificadas existencialmente (∃) especificadas en la condición, se convierte la cláusula a una sentencia SELECT columna FROM tabla; con los nombres de la columna y la tabla que aparecen en la misma cláusula.
• En el caso de tener funciones agregadas MIN, MAX, SUM, COUNT, o AVG se transformarán estas funciones a SQL con la especificación de la sentencia GROUP_BY clave HAVING condición. Donde clave es la clave compartida entre las tablas, y condición es la condición que gobierna la agrupación, como sucede en RI4.