Restricciones y Triggers Las restricciones son declaraciones de condiciones sobre la base de datos que debe permanecer verdaderas. Éstas incluyen restricciones basado en atributos, en tuplas, en llaves, y restricciones de integridad de referencial. El sistema inspecciona la violación de las restricciones sobre acciones que pueden causar una violación, y aborta la acción de acuerdo con lo especificado en la restricción. La información sobre las restricciones del SQL puede encontrarse en el libro de texto. La implementación de restricciones en Oracle difiere del standard SQL, ver documento [1] Los disparadores (o triggers) son una estructura especial del PL/SQL similar a los procedimientos. Sin embargo, un procedimiento se ejecuta explícitamente desde otro bloque vía un procedimiento de llamado, mientras un disparador se ejecuta implícitamente siempre que el evento activando ocurra. El evento que activa el disparador es una orden de INSERCIÓN (INSERT), de BORRADO (DELETE), o de ACTUALIZACION (UPDATE). La elección del momento adecuado o puede ser ANTES o DESPUÉS DE (BEFORE o AFTER). El trigger puede estar definido a nivel de fila o de instrucción, donde los primeros realizan la acción una vez para cada fila afectada en el evento y los segundos realizan la acción una vez por toda la instrucción. ● Restricciones: ● Difiriendo la comprobación de una restricción ● Violación de una Restricción ● Triggers: ● Sintaxis Básica de un Trigger ● Ejemplo de Trigger ● Desplegando la Definición de Errores en el Trigger ● Viendo los Triggers Definidos ● Borrando Triggers ● Desactivando Triggers ● Abortando Triggers con Error ● Mutating Table Errors Difiriendo La Comprobación de una Restricción A veces es necesario diferir la comprobación de ciertas restricciones, como en el problema " del pollo-y-el-huevo". Suponga que nosotros queremos decir: CREATE TABLE Pollo (polloID INT PRIMARY KEY, huevoID INT REFERENCES Huevo(huevoID)); CREATE TABLE Huevo (huevoID INT PRIMARY KEY, polloID INT REFERENCES Pollo(polloID)); Pero si tecleamos las declaraciones anteriores en Oracle, nosotros conseguiremos un error. ¡La razón es que la instrucción CREATE TABLE para Pollo se refiere a la tabla Huevo , que no se ha creado todavía! Creando el Huevo no ayudarán mucho, porque el Huevo se refiere para un Pollo . Para trabajar alrededor de este problema, nosotros necesitamos comandos que permitan la modificación del esquema SQL. Primero, creamos el Pollo sin las declaraciones de llave foránea: CREATE TABLE Pollo (polloID INT PRIMARY KEY, huevoID INT); CREATE TABLE Huevo (huevoID INT PRIMARY KEY, polloID INT); Luego adicionamos las restricciones de llave foránea: ALTER TABLE Pollo ADD CONSTRAINT polloREFHuevo FOREIGN KEY (huevoID) REFERENCES Huevo (huevoID) INITIALLY DEFERRED DEFERRABLE; ALTER TABLE Huevo ADD CONSTRAINT huevoREFPollo FOREIGN KEY (polloID) REFERENCES Pollo (polloID) INITIALLY DEFERRED DEFERRABLE; INITIALLY DEFERRED DEFERRABLE le dice a Oracle que haga la comprobación de restricción diferida. Por ejemplo, para insertar (1, 2) en el pollo y (2, 1) en el huevo, nosotros usamos: INSERT INTO Pollo VALUES(1, 2); INSERT INTO Huevo VALUES(2, 1); COMMIT; Dado que hemos declarado las restricciones de llave foránea como "diferidas", ellos sólo se verifican en el punto de Commit. (Sin diferir la verificación de la restricción, nosotros no podemos insertar nada en las tablas Pollo y Huevo, porque el primer INSERT siempre viola la restricción). Finalmente, para borrar las tablas, primero tenemos que borrar las restricciones, porque Oracle no permite borrar una tabla que esta siendo referenciada por otra tabla. ALTER TABLE Huevo DROP CONSTRAINT huevoREFPollo; ALTER TABLE Pollo DROP CONSTRAINT polloREFHuevo; DROP TABLE Huevo; DROP TABLE Pollo; Violación de una Restricción En general, Oracle devuelve un mensaje del error cuando una restricción se viola. Específicamente para los usuarios de JDBC, esto significa que se devuelve una SQLException. Los programadores deben usar la instrucción WHENEVER y/o verificar el contenido del SQLCA (para los usuarios de Pro*C) o capturar la excepción SQLException (para usuarios de JDBC) para conseguir el código de error devuelto por Oracle. Algunos números del código de error específicos son: 1 para las violaciones de restricciones de llave primaria, 2291 para las violaciones de llaves foráneas, 2290 para las violaciones de restricciones CHECK de atributo y de tupla. Oracle también proporciona mensaje de error simples, que tienen un formato similar a lo siguiente: ORA-02290: check constraint (YFUNG.GR_GR) violated o ORA-02291: integrity constraint (HONDROUL.SYS_C0067174) violated - parent key not found Para más detalles sobre cómo es el manejo de errores, por favor eche una mirada al “tratamiento de errores de Pro*C” o a la sección de las Recuperando Excepciones de “tratamiento de errores de JDBC”. Sintaxis Básica de un Trigger A continuación se presenta la sintaxis para crear un disparador en Oracle (difiere ligeramente de la sintaxis del SQL Standard): CREATE [OR REPLACE] TRIGGER < nombre_trigger > {BEFORE|AFTER} {INSERT|DELETE|UPDATE} ON <NombreTabla> [REFERENCING [NEW AS <nuevo_Nombre_Fila >] [OLD AS <Viejo_Nombre_Fila>]] [FOR EACH ROW [WHEN (<condicion_Del_Trigger >)]] <Cuerpo_Del_Trigger> Algunos puntos importantes para notar: ● Se pueden crear triggers para tablas, BEFORE y AFTER. ( Triggers INSTEAD OF solo están disponibles para vistas; típicamente ellos se usan para llevar a cabo actualizaciones de vistas.) ● Se pueden especificar hasta tres eventos de disparo usando el conector OR. Además, UPDATE puede ser opcionalmente seguido de la palabra clave OF y una lista de atributos en <NombreTabla>. Si esta presente la cláusula OF define que el evento solo será una actualización del atributo(s) listados después del OF. Aquí son algunos ejemplos: ... INSERT ON R ... ... INSERT OR DELETE OR UPDATE ON R ... ... UPDATE OF A, B OR INSERT ON R ... ● Si se especifica la opción FOR EACH ROW, el trigger es a nivel de fila; De otra forma el trigger será a nivel de instrucción.. ● Sólo para triggers a nivel de fila: ● Las variables especiales NEW y OLD están disponibles para referirse respectivamente a las tuples nueva y vieja. Nota: En el cuerpo del disparador, NEW y OLD deben ser precedidos por dos puntos (":"), pero en la cláusula WHEN, ellos no tienen los dos puntos precedentes! Vea el ejemplo debajo. ● La cláusula REFERENCING puede ser usada para asignar alias a las variables NEW y OLD. ● Una restricción trigger puede ser especificada en la cláusula WHEN, encerrada entre paréntesis. La restricción del disparador es una condición SQL que debe satisfacerse para que Oracle dispare el trigger. Esta condición no puede contener subconsultas. Sin la cláusula WHEN, el trigger se dispara para cada fila. ● El <Cuerpo_Del_Trigger> es un bloque PL/SQL, en lugar de una secuencia de instrucciones SQL. Oracle ha puesto ciertas restricciones sobre lo que usted puede hacer en el <Cuerpo_Del_Trigger>, para evitar situaciones dónde un disparador realiza una acción que activa un segundo trigger que entonces activa un tercero que podría crear, potencialmente, una vuelta infinita y así sucesivamente. Las restricciones sobre el <Cuerpo_Del_Trigger> incluyen: ● ● ● Usted no puede modificar la misma relación cuya modificación es el evento que activa el disparador. Usted no puede modificar una relación conectada a la relación del disparador por otra restricción como una llave foránea. Ejemplo de Trigger Nosotros ilustramos la sintaxis de Oracle por crear un disparador a través de un ejemplo basado en las siguiente dos tablas: CREATE TABLE TA (a INTEGER, b CHAR(10)); CREATE TABLE TB (c CHAR(10), d INTEGER); Nosotros creamos un disparador que puede insertar un tupla en TB cuando un tupla se inserta en TA. Específicamente, el disparador verifica si la nueva tupla tiene un primer componente 10 o menos, y en ese caso inserta la tupla inversa en TB: CREATE TRIGGER dispara1 AFTER INSERT ON TA REFERENCING NEW AS nuevaFila FOR EACH ROW WHEN (nuevaFila.a <= 10) BEGIN INSERT INTO TB VALUES(:nuevaFila.b, : nuevaFila.a); END dispara1; . run; Note que la instrucción CREATE TRIGGER la finalizamos con punto y run, como en todas las declaraciones de PL/SQL. La ejecución de la instrucción CREATE TRIGGER solo crea el trigger, no lo ejecuta. Sólo un evento de activación, como una inserción en TA (para este ejemplo), causa que el trigger se ejecute. Desplegando la Definición de Errores en el Trigger Como en los procedimientos de PL/SQL, si usted obtiene un mensaje Warning: Trigger created with compilation errors. usted puede ver los mensajes del error tecleando show errors trigger <trigger_name>; Alternativamente, usted puede digitar, SHO ERR (abreviando por SHOW ERRORS) para ver el más reciente error de la compilación. Observe que el número de la línea donde se reporta que ocurren los errores ocurren no es exacta. Viendo los Triggers Definidos Para ver una lista de todos los disparadores definidos, se usa la consulta: select trigger_name from user_triggers; Para más detalles sobre un trigger en particular: select trigger_type, triggering_event, table_name, referencing_names, trigger_body from user_triggers where trigger_name = '<Nombre_Trigger>'; Borrando Triggers Para borrar un trigger ejecuto la instrucción: drop trigger <Nombre_Trigger>; Desactivando Triggers Para desactivar o activar un trigger: alter trigger <Nombre_Trigger> {disable|enable}; Abortando Triggers con Error Los disparadores pueden usarse a menudo para reforzar a una restricción. La cláusula WHEN o el cuerpo del disparador pueden verificar la violación de ciertas condiciones y pueden señalar un error que usando la función incorporada RAISE_APPLICATION_ERROR del Oracle. La acción que activó el trigger (la inserción, actualización, o borrado) se abortaría. Por ejemplo, el siguiente trigger refuerza la restricción de que la edad de la persona sea mayor o igual que cero ( Persona.edad >= 0): create table Persona (edad int); CREATE TRIGGER ChequeaEdadPersona AFTER INSERT OR UPDATE OF edad ON Persona FOR EACH ROW BEGIN IF (:new.edad < 0) THEN RAISE_APPLICATION_ERROR(-20000, 'No se permite una edad negativa'); END IF; END; . RUN; Si intentamos ejecutar la inserción: insert into Person values (-3); Obtendremos el siguiente mensaje de error: ERROR at line 1: ORA-20000: No se permite una edad negativa ORA-06512: at "MYNAME.CHEQUEAEDADPERSONA", line 3 ORA-04088: error during execution of trigger 'MYNAME.CHEQUEAEDADPERSONA' y nada se insertará. En general, los efectos del trigger y la declaración de activación son reversadas.. Errores de Tabla Mutante A veces usted puede encontrar que Oracle informa un" error de tabla mutante" ("mutating table error") cuando su trigger se ejecuta. Esto sucede cuando el disparador está preguntando o está modificando una" tabla mutante" la cual es la tabla cuya modificación activó el disparador o una tabla que podría necesitar actualizarse debido a una restricción de llave foránea con una política en cascada (CASCADE). Para evitar errores de tabla mutante: ● Un trigger a nivel de fila no debe preguntar o modificar una tabla mutante. (por su puesto que NEW y OLD todavía puede accederse por el disparador.) ● Un trigger a nivel de instrucción no debe preguntar o modificar una tabla mutante si el trigger se dispara como resultado de un borrado en cascada. Referencias [1] http://www-db.stanford.edu/~ullman/fcdb/oracle/or-nonstandard.html#constraints