1
Autor: Luis Felipe Felipe Wanumen Wanumen Silva
ENCICLOPEDIA CONOCIENDO
TOMO XLII CUADERNO DE TRIGGERS
ELABORADO POR: LUIS FELIPE WANUMEN SILVA INGENIERO DE SISTEMAS Y AUTOR DE LA ENCICLOPEDIA CONOCIENDO
CONTIENE RESUMEN DE SUS CLASES, AMPLIACIÓN DE EXPLICACIONES, EJERCICIOS RESUELTOS DE TRIGGERS Y ALGO MÁS.
PRIMERA VERSIÓN AUN NO REVISADA SANTAFE DE BOGOTA D.C.
Enciclopedia Conociendo
Cuaderno de Triggers
2
Autor: Luis Felipe Felipe Wanumen Wanumen Silva
AGOSTO DE 2003.
Enciclopedia Conociendo
Cuaderno de Triggers
3
Autor: Luis Felipe Felipe Wanumen Wanumen Silva
1. PRIMERA UNIDAD: INTRODUCCIÓN A LOS TRIGGERS 1.1. 1.1. DEF INICIÓN Los triggers son procedimientos almacenados que se disparan cuando se presenta una acción sobre una tabla, como una inserción, una actualización o un borrado de registros. En la definición del trigger, se especifica que acciones desencadenan la ejecución del trigger.
1. 2. OB OB J ETIVO ETIVO DE LOS TRIG GE R S Validar inserciones, actualizaciones o modificaciones, pero es bien importante mencionar que tales validaciones por pura estética y coherencia con el lenguaje SQL, no deberían ser validaciones que se puedan hacer directamente en la definición de la tabla. Por ejemplo validar que una entrada para un campo sea: SI o NO, es algo que debería hacerse directamente en la definición de la tabla y no se justifica la creación de un trigger para llevar a cabo dicha tarea, pero bueno, no falta el diseñador de bases de datos que hace este tipo de cosas. (Recordemos que las restricciones a nivel de integridad es mejor realizarlas directamente en la definición de campos de una tabla)
1. 3. CONSIDER CONSIDER ACIONES ACIONES SOBR E TRIGG ER S Es importante disminuir la cantidad de instrucciones al máximo asociadas con un trigger, debido a que si la base de datos está en operación y recibe varias peticiones de actualización, inserción o actualización, puede verse seriamente afectada el rendimiento del sistema. Recordemos que en un trigger asociado a una tabla, es posible realizar operaciones de inserción, actualización o borrado de registros de otras tablas. En estos casos es conveniente tener en cuenta que posiblemente al realizar dichas operaciones sobre otras tablas, se disparen otros triggers definidos para dichas tablas, lo cual podría llegar incluso a generar un bloqueo, si no se hace un verdadero análisis de las dependencias de los diversos triggers. En el caso fortuito que un trigger tenga demasiadas instrucciones y no se pueda eliminar su cantidad, es aconsejable que se coloquen dichas instrucciones en un procedimiento almacenado y se llame a dicho procedimiento desde el trigger, debido a que el procedimiento almacenado es compilado cuando se guarda y esto hace que se acelere la ejecución del trigger.
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Felipe Wanumen Wanumen Silva
4
2. SEGUNDA UNIDAD: TRIGGERS Y MAS CONCEPTOS BASICOS
2.1. 2.1 . UN TR IG G E R S I E MPR MP R E S E B OR R A C UA N D O S E B OR R A L A TAB LA ASOCIADA Una de las cosas mas interesantes a tener en cuenta en los triggers, es que los triggers se asocian con una tabla y al eliminarse la tabla a la que se relaciona dicho trigger, se elimina el trigger. Al reves no ocurre, es decir cuando se elimina un trigger no se elimina la tabla a la que hace relacion el trigger. Esta cuestion es una cosa bastante interesante. Explicamos esto mediante el siguiente ejercicio: Creemos la siguiente tabla: create table usuario( codigo int int,, nombre nvarchar (50) (50) ) Creemos por ejemplo un trigger de inserción llamado “trigger_asociado” relacionado con la
tabla usuario: create trigger trigger_asociado on usuario for insert as begin select * from usuario end Vemos que podemos borrar el trigger con la instrucción: drop trigger trigger_asociado y finalmente podemos eliminar la tabla “usuario”:
drop table usuario y no salen errores, tal como se muestra a continuación: El problema comienza cuando borramos primero la tabla usuario y luego intentamos borrar el trigger llamado “trigger_asociado”, es decir cuand o intentamos ejecutar las sentencias en
el siguiente orden: drop table usuario drop trigger trigger_asociado Enciclopedia Conociendo
Cuaderno de Triggers
5
Autor: Luis Felipe Felipe Wanumen Wanumen Silva
obtenemos un resultado similar al siguiente: Cannot drop the trigger 'trigger_asociado', because it does not exist in the system catalog. En el cual podemos apreciar que si eliminamos la tabla, se eliminan con ella los triggers asociados.
2.2. 2.2 . UN TR I G G E R D E B E S E R L A P R I ME R A S E NTE NT E NC I A E N A R C HI V O DE LOTES LOTES Supongamos que tenemos el siguiente script: create table usuario( codigo int int,, nombre nvarchar (50) (50) ) create trigger trigger_asociado on usuario for insert as begin select * from usuario end y lo intentamos ejecutar todo junto tal como muestra la siguiente figura:
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
6
Observamos que se producen errores, con lo cual para ejecutar dicho script, se requiere ejecutarlo por partes, es decir: Primero ejecutar la creación de la tabla:
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
7
Y después la creación del trigger:
2.3. ME NS A J E S E N LA INS E R C IÓ N Dada la siguiente tabla: Tabla: Continente
Id nombre --- --La cual es creada con las siguientes instrucciones: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50)); queremos que se muestre un mensaje en el analizador de consultas cada vez que el se intente insertar un registro en la tabla. Para cumplir dicho cometido, es necesario crear un trigger similar al siguiente: create trigger inserta_continente Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
8
on CONTINENTE for insert as print "Insertando en tabla CONTINENTE" Con lo cual al intentar realizar la siguiente inserción: insert into CONTINENTE values('AFRICA') obtenemos el siguiente resultado: Insertando en tabla CONTINENTE (1 filas afectadas)
2.4. ME NS A J E S E N LA A C TUA LIZA C IÓN Dada la siguiente tabla: Tabla: Continente
Id 1 2 3 4 5
nombre AFRICA EUROPA HACIA AMERICA OCEANIA
(0 filas afectadas) La cual es creada con las siguientes instrucciones: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50)); insert into CONTINENTE values('AFRICA') insert into CONTINENTE values('EUROPA') insert into CONTINENTE values('HACIA') insert into CONTINENTE values('AMERICA') insert into CONTINENTE values('OCEANIA') queremos que cada vez que se intente realizar una actualización se muestre un mensaje. Para lograr esto, es necesario crear un trigger similar al siguiente: create trigger actualiza_continente Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
9
on CONTINENTE for update as print "Actualizando CONTINENTE" De tal suerte que cuando se intenta realizar la actualización sobre el continente “HACIA”
tal como se muestra a continuación: update CONTINENTE set nombre = 'ASIA' where id = 3 Obtenemos el siguiente resultado: Actualizando CONTINENTE (1 filas afectadas)
2.5. ME NS A J E S E N E L B OR R A DO DE R E G IS TR OS Dada la siguiente tabla: Tabla: Continente
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA AMERICA OCEANIA RUSIA
La cual es creada con las siguientes instrucciones: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50)); insert into CONTINENTE values('AFRICA') insert into CONTINENTE values('EUROPA') insert into CONTINENTE values('HACIA') insert into CONTINENTE values('AMERICA') insert into CONTINENTE values('OCEANIA') insert into CONTINENTE values('RUSIA') Enciclopedia Conociendo
Cuaderno de Triggers
10
Autor: Luis Felipe Wanumen Silva
Queremos que cada vez que el se intente eliminar un registro de la tabla “CONTINENTE”,
se despliegue un mensaje que muestre el registro borrado(el cual se encuentra en la tabla “deleted”), después se muestre la tabla “CONTINENTE” en la cual se pueda apreciar que
ya se ha realizado el borrado de dicho registro. Por último, que se deshaga la operación de borrado y se vuelva a mostrar la tabla “CONTINENTE” para ver ificar que efectivamente se deshizo la operación. Veamos pues el trigger: create trigger elimina_continente on CONTINENTE for delete as if (select count(*) from CONTINENTE, deleted where CONTINENTE.id = deleted.id) = 0 begin print "Eliminando el(los) CONTINENTE(S)" select * from deleted print "La tabla CONTINENTE es:" select * from CONTINENTE print "Se deshizo la eliminación" rollback transaction select * from CONTINENTE end Ahora bien, para probar el trigger, realizamos cualquier operación de eliminación de cualquier registro de la tabla “CONTINENTE”, tal como se muestra a continuación:
delete from CONTINENTE where id = 1 El resultado de dicha operación de eliminación es el siguiente: Eliminando el(los) CONTINENTE(S) id nombre 1 AFRICA (1 filas afectadas) La tabla CONTINENTE es:
id nombre 2 EUROPA 3 HACIA Enciclopedia Conociendo
Cuaderno de Triggers
11
Autor: Luis Felipe Wanumen Silva
4 5 6
AMERICA OCEANIA RUSIA
(5 filas afectadas) Se deshizo la eliminación
id 1 2 3 4 5 6
nombre AFRICA EUROPA HACIA AMERICA OCEANIA RUSIA
(6 filas afectadas)
Para comprender mejor el ejercicio anteriormente mencionado es importante tener en cuenta que cada vez que se borra un registro, dicho registro se pasa a una tabla temporal denominada “deleted”, con lo cual en dicha tabla podemos consultar los registros que fueron borrados
También es importante notar que la instrucción: rollback transaction Deshace la(s) acción(es) anteriormente ejecutada(s).
3. TERCERA UNIDAD: TRIGGERS DE INSERCIÓN
3.1. L A TA B LA INS E R TE D La tabla “inserted” es una tabla temporal que se crea cuando se insertan datos. Para el ejercicio siguiente se parte de la tabla “continente ” cuyos datos son:
Enciclopedia Conociendo
Cuaderno de Triggers
12
Autor: Luis Felipe Wanumen Silva
Tabla: Continente
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA AMERICA OCEANIA RUSIA
La cual es creada con las siguientes instrucciones: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50)); insert into CONTINENTE values('AFRICA') insert into CONTINENTE values('EUROPA') insert into CONTINENTE values('HACIA') insert into CONTINENTE values('AMERICA') insert into CONTINENTE values('OCEANIA') insert into CONTINENTE values('RUSIA') Queremos que se cada vez que se vaya a realizar una inserción, se le muestre al usuario los datos que se van a insertar y posteriormente se muestra la tabla “CONTINENTE” con los datos insertados. Para lograr esto, es necesario crear un trigger de inserción similar al siguiente: create trigger inserta_continente on CONTINENTE for insert as begin print "El(los) dato(s) a insertar:" select * from inserted print "La tabla con datos insertados:" select * from CONTINENTE end De tal suerte que al intentar ejecutar la sentencia de inserción siguiente: insert into CONTINENTE values('AMERICA DEL SUR') Se produzca el siguiente resultado: El(los) dato(s) a insertar: Enciclopedia Conociendo
Cuaderno de Triggers
13
Autor: Luis Felipe Wanumen Silva
id nombre 7 AMERICA DEL SUR (1 filas afectadas) La tabla con datos insertados:
id 1 2 3 4 5 6 7
nombre AFRICA EUROPA HACIA AMERICA OCEANIA RUSIA AMERICA DEL SUR
(7 filas afectadas) Observemos que para mostrar los datos a insertar, simplemente basta con hacer una consulta sobre la tabla “inserted”, pues como se ha mencionado en este apartado, dicha
tabla, contiene los registros que se van a insertar. Posteriormente al hacer la consulta sobre la tabla “CONTINENTE”, se muestra dicha tabla con el registro insertado, puesto que
dicha tabla fue actualizada con la sentencia ejecutada por el usuario.
3.2. I NS E R C IÓN C ON DICION A L Dada la tabla siguiente: Tabla: Continente
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA AMERICA OCEANIA RUSIA
La cual es creada con las siguientes instrucciones: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50));
Enciclopedia Conociendo
Cuaderno de Triggers
14
Autor: Luis Felipe Wanumen Silva
insert into CONTINENTE values('AFRICA') insert into CONTINENTE values('EUROPA') insert into CONTINENTE values('HACIA') insert into CONTINENTE values('AMERICA') insert into CONTINENTE values('OCEANIA') insert into CONTINENTE values('RUSIA') Queremos hacer un trigger que impida la inserción de nombres de continentes existentes, con el ánimo de impedir duplicación de información. Un trigger que impide la inserción de nombres de continentes existentes en la tabla “CONTINENTE” es:
create trigger inserta_continente on CONTINENTE for insert as if (select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 begin print "Ya existe este continente:" select * from inserted print "Por tanto los datos no se insertaron:" rollback transaction select * from CONTINENTE end Para comprobar que este trigger realiza el trabajo deseado, sencillamente ejecutemos la siguiente instrucción: insert into CONTINENTE values('AMERICA') y obtendremos el siguiente resultado: Por tanto los datos no se insertaron: Ya existe este continente:
id nombre 7 AMERICA (1 filas afectadas)
id 1 2 3 4 5 6
nombre AFRICA EUROPA HACIA AMERICA OCEANIA RUSIA
(6 filas afectadas)
Enciclopedia Conociendo
Cuaderno de Triggers
15
Autor: Luis Felipe Wanumen Silva
Para comprender este ejercicio es necesario tener en cuenta que cuando se va a realizar la inserción, se crea una tabla temporal denominada “inserted”, la cual tiene los siguientes datos: Tabla: inserted
id nombre 7 AMERICA También es bueno tener en cuenta que los datos ya han sido insertados y por tanto al ejecutar la sentencia siguiente en el trigger: Select * from CONTINENTE Obtendremos el resultado:
id 1 2 3 4 5 6 7
nombre AFRICA EUROPA HACIA AMERICA OCEANÍA RUSIA AMERICA
Con lo cual podemos observar que aparece dos veces el nombre de continente “AMERICA”, por tanto el trigger debe mostrar los campos de la tabla “CONTINENTE” que tengan en su parte “nombre” el valor “AMERICA” y dicho valor “AMERICA”, es posible obtenerlo de la tabla “inserted”. Por tanto la pregunta que se debe realizar es:
if (select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 De acuerdo al resultado de dicha pregunta, se debe proceder a deshacer la transacción, tal como se muestra a continuación: rollback transaction El resto de instrucciones, simplemente están mostrando al usuario el campo que no se pudo insertar, simplemente haciendo una consulta sobre la tabla “inserted”
3.3. OR DE N DE E J E C UC IÓN C ON TR IG G E R S
Enciclopedia Conociendo
Cuaderno de Triggers
16
Autor: Luis Felipe Wanumen Silva
El orden en que se ejecutan las sentencias cuando existen trigger asociados a un tipo de consulta es el siguiente: o o o o
Se crean las tablas temporales Se ejecuta la consulta Se ejecuta el trigger Se borran las tablas temporales
3.4. IMP ID IE ND O VA LOR E S NO E XIS TE NTE S En el trigger de la sección anterior, se estaba realizando un ejercicio para impedir que el usuario insertara valores repetidos. En esta oportunidad se va a realizar un trigger para evitar que el usuario inserte valores que no estén repetidos. Para este ejercicio, partimos de la siguiente tabla:
Id 1 2 3 4 5
nombre AFRICA EUROPA ASIA OCEANIA RUSIA
planeta TIERRA TIERRA TIERRA TIERRA TIERRA
La cual fue creada con las siguientes sentencias: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50) null, planeta nvarchar (50) null); insert into CONTINENTE values('AFRICA', 'TIERRA') insert into CONTINENTE values('EUROPA', 'TIERRA') insert into CONTINENTE values('ASIA', 'TIERRA') insert into CONTINENTE values('OCEANIA', 'TIERRA') insert into CONTINENTE values('RUSIA', 'TIERRA') Queremos impedir que se inserten planetas que no estén incluidos en la tabla y al mismo tiempo impedir que se inserten nombres de continentes que ya existan previamente. Es decir que existen dos condiciones: Condición 1 Impedir que se inserten planetas no incluidos en la tabla “CONTINENTE”
Condición 2 Impedir que se inserten nombres de continentes existentes. Enciclopedia Conociendo
Cuaderno de Triggers
17
Autor: Luis Felipe Wanumen Silva
Cada una de estas condiciones puede verificarse mediante las instrucciones:
Condición 1: Condición 2 Impedir que se inserten planetas no Impedir que se inserten nombres de incluidos en la tabla “CONTINENTE” continentes existentes. select count(*) select count(*) from CONTINENTE, inserted from CONTINENTE, inserted where CONTINENTE.planeta like where CONTINENTE.nombre like inserted.planeta )=1 inserted.nombre)=2 Explicación: Cuando se inserta uno que no Explicación: Cuando se inserta uno que ya existe, quiere decir que está en la tabla una existe, está en realidad dos veces sola vez Podemos entonces utilizar el operador “OR” para validar si se presenta alguna de estas
condiciones, con el ánimo de lograr que el if sea válido. El problema radica cuando ambas condiciones sean ciertas, el “OR” no entra debido a que el “OR” es válido solo cuando
alguna es válida, no cuando todas son válidas. Por ello, es necesario agregar una tercera condición: Condición 3: Que se cumplan simultáneamente las dos condiciones. Con todas las explicaciones anteriores, podemos decir que el trigger que realiza el trabajo deseado es algo similar al siguiente: create trigger inserta_continente on CONTINENTE for insert as if (select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 OR (select count(*) from CONTINENTE, inserted where CONTINENTE.planeta like inserted.planeta )=1 OR ((select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 AND (select count(*) from CONTINENTE, inserted where CONTINENTE.planeta like inserted.planeta )=1) begin print "Ya existe este continente:" print "O planeta no existe" Enciclopedia Conociendo
Cuaderno de Triggers
18
Autor: Luis Felipe Wanumen Silva
select * from inserted print "Por tanto los datos no se insertaron:" rollback transaction select * from CONTINENTE end Con lo cual al ejecutar las siguientes sentencias en el orden especificado a continuación tenemos los siguientes resultados: Sentencia 1
insert into CONTINENTE values('AMERICA DEL NORTE', 'TIERRA') -- BIEN (1 filas afectadas) Sentencia 2
insert into CONTINENTE values('AMERICA DEL NORTE', 'TIERRA1') -- MAL Ya existe este continente: O planeta no existe
id nombre planeta 7 AMERICA DEL NORTE TIERRA1 (1 filas afectadas) Por tanto los datos no se insertaron: id nombre planeta 1 AFRICA TIERRA 2 EUROPA TIERRA 3 ASIA TIERRA 4 OCEANIA TIERRA 5 RUSIA TIERRA 6 AMERICA DEL NORTE TIERRA (6 filas afectadas) Sentencia 3
insert into CONTINENTE values('AMERICA DEL NORTE', 'TIERRA') -- MAL Ya existe este continente: O planeta no existe
id nombre planeta 8 AMERICA DEL NORTE TIERRA Enciclopedia Conociendo
Cuaderno de Triggers
19
Autor: Luis Felipe Wanumen Silva
(1 filas afectadas) Por tanto los datos no se insertaron:
id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA OCEANIA RUSIA AMERICA DEL NORTE
planeta TIERRA TIERRA TIERRA TIERRA TIERRA TIERRA
(6 filas afectadas) Sentencia 4
insert into CONTINENTE values('AMERICA DEL SUR', 'JUPITER') -- MAL Ya existe este continente: O planeta no existe
id nombre planeta 9 AMERICA DEL SUR JUPITER (1 filas afectadas) Por tanto los datos no se insertaron:
id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA OCEANIA RUSIA AMERICA DEL NORTE
planeta TIERRA TIERRA TIERRA TIERRA TIERRA TIERRA
(6 filas afectadas) Sentencia 5
insert into CONTINENTE values('AMERICA DEL SUR', 'TIERRA') -- BIEN (1 filas afectadas)
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
Enciclopedia Conociendo
20
Cuaderno de Triggers
21
Autor: Luis Felipe Wanumen Silva
4. CUARTA UNIDAD: TRIGGERS DE ACTUALIZACIÓN
4.1. CONCE PTUALMENTE COMPR ENDIENDO LOS TRIG GE R S DE A C TUA LIZAC ION Es importante comprender que los triggers de actualización son una de las cosas mas interesantes en cuanto a triggers se refiere, por cuanto a diferencia de los triggers de inserción, no solamente crean una tabla “inserted”, sino que también crean una tabla “deleted”, en la cual copian los datos que estaban antes de la actualización.
Conceptualmente el siguiente diagrama muestra el orden en el que el motor de bases de datos SQL Server ejecuta las instrucciones cuando existen triggers de actualización: Recibe una consulta de actualizacion
SI
Verifica si existen triggers de actualizacion
Crea una tabla inserted y otra deleted Copia los datos que se estan en el registro que se quiere actualizar en la tabla deleted
NO Ejecuta la consulta de actualizacion
Copia los datos que estan en el registro que se actualizara en la tabla inserted Ejecuta la consulta de actualizacion Ejecuta el trigger de actualizacion Borra las tablas inserted y deleted
Enciclopedia Conociendo
FIN
Cuaderno de Triggers
22
Autor: Luis Felipe Wanumen Silva
4.2. COLUMNAS AC TUALIZADA S E N UNA A CTUALIZAC IÓN A primera vista, el título de este apartado suena un poco confuso, pero en realidad se refiere a la siguiente pregunta: ¿Se actualizó un valor para una columna en una sentencia de actualización? Para comprender mejor esta pregunta, a continuación se muestra la tabla CONTINENTE:
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA OCEANIA RUSIA AMERICA CENTRAL
planeta TIERRA TIERRA TIERRA TIERRA TIERRA TIERRA
Creada con las sentencias: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50) null, planeta nvarchar (50) null); insert into CONTINENTE values('AFRICA', 'TIERRA') insert into CONTINENTE values('EUROPA', 'TIERRA') insert into CONTINENTE values('ASIA', 'TIERRA') insert into CONTINENTE values('OCEANIA', 'TIERRA') insert into CONTINENTE values('RUSIA', 'TIERRA') insert into CONTINENTE values('AMERICA CENTRAL', 'TIERRA') A la cual le asociamos el siguiente trigger de actualización CREATE TRIGGER inserta_continente ON CONTINENTE FOR UPDATE AS IF UPDATE(planeta) PRINT 'La columna PLANETA fue actualizada' Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
23
Ahora bien, para poder entender completamente el trigger, es necesario aclarar que la sentencia: IF UPDATE(planeta) Pregunta si se actualizó la columna “planeta” de la tabla “CONTINENTE”, en caso ser
afirmativa la respuesta, se muestra un mensaje y se realiza la actualización. Por ejemplo, al ejecutar la siguiente sentencia: update CONTINENTE set nombre = 'AMERICA' where id = 6 Se produce el siguiente mensaje por parte de SQL Server: (1 filas afectadas) Y la tabla, queda como se muestra a continuación:
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA OCEANIA RUSIA AMERICA
planeta TIERRA TIERRA TIERRA TIERRA TIERRA TIERRA
Lo que indica que solamente se actualizó la tabla, pero no se mostró el mensaje, debido a que en la sentencia de actualización no estaba presente el campo “planeta”. Ahora bien, la
siguiente sentencia: update CONTINENTE set nombre = 'AMERICA', planeta = 'TIERRA' where id = 6 Produce el siguiente resultado: La columna PLANETA fue actualizada (1 filas afectadas) Y la tabla queda como se muestra a continuación:
Enciclopedia Conociendo
Cuaderno de Triggers
24
Autor: Luis Felipe Wanumen Silva
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA OCEANIA RUSIA AMERICA CENTRAL
planeta TIERRA TIERRA TIERRA TIERRA TIERRA TIERRA
Lo que quiere decir que a pesar de haber actualizado el campo “planeta” con el mismo valor que tenía “TIERRA”, de todas formas, el hecho de haberlo incluido en la sentencia de
actualización, de pie para que SQL Server lo tome como un campo modificado.
4.3. TAR EA DE INVES TIGA CIÓN
Para los estudiantes / lectores que deseen utilizar funciones propias de SQL Server, se les sugiere que utilice la función: IF COLUMNS_UPDATED() Para ver que con esta función se logra el mismo efecto que con IF UPDATE(planeta) La tarea consiste en explicar como funciona dicha sentencia.
4.4. TR IGG ER DE A CTUALIZAC IÓN CONFUS O Dada la tabla CONTINENTE:
Id 1 2 3 4 5 6
nombre AFRICA EUROPA ASIA OCEANIA RUSIA AMERICA CENTRAL
Enciclopedia Conociendo
planeta TIERRA TIERRA TIERRA TIERRA TIERRA TIERRA
Cuaderno de Triggers
25
Autor: Luis Felipe Wanumen Silva
Creada con las sentencias: create table CONTINENTE( --crea la tabla CONTINENTE id int identity(1,1) primary key, nombre nvarchar (50) null, planeta nvarchar (50) null); insert into CONTINENTE values('AFRICA', 'TIERRA') insert into CONTINENTE values('EUROPA', 'TIERRA') insert into CONTINENTE values('ASIA', 'TIERRA') insert into CONTINENTE values('OCEANIA', 'TIERRA') insert into CONTINENTE values('RUSIA', 'TIERRA') insert into CONTINENTE values('AMERICA CENTRAL', 'TIERRA') A la cual le asociamos el siguiente trigger de actualización CREATE TRIGGER inserta_continente ON CONTINENTE FOR UPDATE AS IF UPDATE(planeta) and NOT UPDATE(nombre) PRINT 'La columna PLANETA fue actualizada' rollback PRINT 'Se dehizo la operacion' Las siguientes sentencias producen los siguientes resultados:
Sentencias update CONTINENTE set nombre = 'AMERICA' where id = 6 update CONTINENTE set nombre = 'AMERICA', planeta = 'TIERRA' where id = 6 update CONTINENTE set planeta = 'TIERRA' where id = 6
Resultados Se dehizo la operación Se dehizo la operación
La columna PLANETA fue actualizada Se dehizo la operación
En realidad todas estas operaciones se deshacen, debido básicamente a que lo que se ejecuta dentro del if es la siguiente sentencia. Cosa diferente fuera, si se tuviera definido el trigger así: CREATE TRIGGER inserta_continente ON CONTINENTE FOR UPDATE AS Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
26
IF UPDATE(planeta) and NOT UPDATE(nombre) BEGIN PRINT 'La columna PLANETA fue actualizada' rollback PRINT 'Se dehizo la operacion' END Tendríamos los siguientes resultados:
Sentencias Resultados update CONTINENTE (1 filas afectadas) set nombre = 'AMERICA' where id = 6
update CONTINENTE (1 filas afectadas) set nombre = 'AMERICA', planeta = 'TIERRA' where id = 6
Update CONTINENTE La columna PLANETA fue set planeta = 'TIERRA' actualizada where id = 6 Se dehizo la operacion
Enciclopedia Conociendo
Tabla CONTINENTE id nombre planeta 1 AFRICA TIERRA 2 EUROPA TIERRA 3 ASIA TIERRA 4 OCEANIA TIERRA 5 RUSIA TIERRA 6 AMERICA TIERRA id nombre planeta 1 AFRICA TIERRA 2 EUROPA TIERRA 3 ASIA TIERRA 4 OCEANIA TIERRA 5 RUSIA TIERRA 6 AMERICA1 TIERRA1 id nombre planeta 1 AFRICA TIERRA 2 EUROPA TIERRA 3 ASIA TIERRA 4 OCEANIA TIERRA 5 RUSIA TIERRA 6 AMERICA1 TIERRA1
Cuaderno de Triggers
27
Autor: Luis Felipe Wanumen Silva
5. PRIMEROS EJERCICIOS SOBRE TRIGGERS En esta seccion se pretende hacer algunos ejercicios basicos de triggers para lograr que el amigo lector / estudiante adquiera las destrezas y comprenda el funcionamiento de los triggers en SQL Server.
5.1. UN P R IME R E J E R C IC IO S OB R E TR IG G E R S DE A C TUA LIZAC ION Creamos una tabla de la siguiente forma: create table usuario( codigo int, nombre nvarchar (50), edad int ) Insertamos algunos datos: insert into usuario values(1,'Luis Felipe Silva',30) insert into usuario values(2,'Ana Sofia Vergara',30) insert into usuario values(3,'Ana Sofia Henao',30) insert into usuario values(4,'Sofia Cabrales',30) insert into usuario values(5,'Cristina Aguilera',30) Creamos el trigger: create trigger triggeractualizacionusuario on usuario for update as begin select * from usuario select * from inserted select * from deleted end Ejecutamos la consulta: update usuario set nombre='Luis Felipe Wanumen Silva' where codigo=1 La cual produce un resultado similar al siguiente:
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
28
Al ejecutar la consulta en seguida: update usuario set nombre='Luis Felipe Silva' where codigo=1 obtenemos un resultado similar al siguiente:
Enciclopedia Conociendo
Cuaderno de Triggers
29
Autor: Luis Felipe Wanumen Silva
Esto nos deja bien en claro que la tabla “inserted” en este tipo de triggers tiene los datos que se van a insertar, que la tabla “deleted” contiene los datos que se van a eliminar y que la tabla “usuario” al interior del trigger tiene los datos actualizados, es decir que primero se
ejecuta la consulta de actualización y luego el trigger de actualización. Podemos borrar el trigger usando: drop trigger triggeractualizacionusuario
5.2. UN S E G UNDO E J E R C IC IO DE TR IG G E R S En este segundo ejercicio se pretende hacer un trigger que impida que se actualice un usuario con un código distinto al que tenia este. Creamos una tabla de la siguiente forma: create table usuario( codigo int, nombre nvarchar (50), edad int ) Insertamos algunos datos: insert into usuario values(1,'Luis Felipe Silva',30) insert into usuario values(2,'Ana Sofia Vergara',30) insert into usuario values(3,'Ana Sofia Henao',30) insert into usuario values(4,'Sofia Cabrales',30) insert into usuario values(5,'Cristina Aguilera',30) Creamos el trigger: create trigger triggeractualizacionusuario1 on usuario for update as declare @codigin1 as int declare @codigin2 as int begin select @codigin1 = (select codigo from inserted) select @codigin2 = (select codigo from deleted) if @codigin1 <> @codigin2 rollback transaction end Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
30
Observamos que la tabla usuario contiene los siguientes datos:
Codigo 1 2 3 4 5
Nombre Luis Felipe Silva Ana Sofia Vergara Ana Sofia Henao Sofia Cabrales Cristina Aguilera
Edad 30 30 30 30 30
Ejecutamos la consulta: update usuario set codigo=7, nombre='Luis Felipe Silva' where codigo=3 y observamos que los datos de la tabla son los siguientes:
Codigo 1 2 3 4 5
Nombre Luis Felipe Silva Ana Sofia Vergara Ana Sofia Henao Sofia Cabrales Cristina Aguilera
Edad 30 30 30 30 30
Con lo cual vemos que los datos siguen siendo los mismos. Es decir que nuestro trigger de actualización propuesto funciona para el caso en el que se desee actualizar un registro con el código igual al que se tenia. Pensamos: ¿Qué pasaría si hacemos una actualización que no actualice el campo código? update usuario set edad=3, nombre='Natalia Paris' where codigo=3 La respuesta es que la consulta funciona y los datos de la tabla serian como los mostrados a continuación:
Codigo 1 2 3
Nombre Luis Felipe Silva Ana Sofia Vergara Natalia Paris
Enciclopedia Conociendo
Edad 30 30 30 Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
4 5
Sofia Cabrales Cristina Aguilera
31
30 30
Podemos borrar el trigger usando: drop trigger triggeractualizacionusuario1
5.3. UN TE R C E R E J E R C IC IO DE TR IG G E R S En esta oportunidad nos proponemos desarrollar un trigger de actualizacion que impida la actualizacion de un registro en su campo nombre por otro que ya exista en la tabla Creamos una tabla de la siguiente forma: create table usuario( codigo int, nombre nvarchar (50), edad int ) Insertamos algunos datos: insert into usuario values(1,'Luis Felipe Silva',30) insert into usuario values(2,'Ana Sofia Vergara',30) insert into usuario values(3,'Ana Sofia Henao',30) insert into usuario values(4,'Sofia Cabrales',30) insert into usuario values(5,'Cristina Aguilera',30) Creamos el trigger: create trigger triggeractualizacionusuario2 on usuario for update as declare @nombrecin1 as nvarchar (50) declare @nombrecin2 as nvarchar (50) declare @codigin1 as int declare @codigin2 as int declare @contador as int begin select @contador = 0 select @nombrecin1 = (select nombre from inserted) select @nombrecin2 = (select nombre from deleted) select @codigin1 = (select codigo from inserted) select @codigin2 = (select codigo from deleted) Enciclopedia Conociendo
Cuaderno de Triggers
32
Autor: Luis Felipe Wanumen Silva
select @contador = (select count(*) from usuario where nombre=@nombrecin1) if @contador>1 rollback transaction end Ahora para probar el trigger hacemos la siguiente consulta: Antes de probar verificamos que tengamos los siguientes datos en la tabla “usuario”:
Codigo 1 2 3 4 5
Nombre Luis Felipe Silva Ana Sofia Vergara Ana Sofia Henao Sofia Cabrales Cristina Aguilera
Edad 30 30 30 30 30
Y ahora ejecutamos la siguiente consulta: update usuario set codigo = 20, nombre = 'Luis Felipe' where codigo=2 Con lo cual verificamos que los datos ahora son los siguientes:
Codigo 1 20 3 4 5
Nombre Luis Felipe Silva Luis Felipe Ana Sofia Henao Sofia Cabrales Cristina Aguilera
Edad 30 30 30 30 30
Esto quiere decir que esta consulta funcion porque esta modificando un nombre “Ana Sofia Vergara” por otro nombre que no existe “Luis Felipe”. Bueno, es probable que el amigo lector diga que si existe otro “Luis Felipe”, pero lo unico cierto es que este primero se llama “Luis Felipe Silva”, en tanto que el que se quiere modificar le falta el “Silva”, recuerde que
como no se usan caracteres comodin la palabra tiene que ser exacta. Pero sin mas rodeos hagamos esta otra consulta de actualizacion: update usuario set codigo = 20, nombre = 'Luis Felipe' where codigo=4 Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
33
en donde observamos que los resultados son: Codigo Nombre Edad 1 Luis Felipe Silva 30 20 Luis Felipe 30 3 Ana Sofia Henao 30 4 Sofia Cabrales 30 5 Cristina Aguilera 30 Es decir que los datos no se pudieron cambiar debido a que se deseaba que la persona con el código 4 quedara con el mismo nombre de la persona que actualmente tenia el código 20 y este tipo de cosas dijimos que era precisamente las que el trigger iba a impedir que se realizaran.
Enciclopedia Conociendo
Cuaderno de Triggers
34
Autor: Luis Felipe Wanumen Silva
6. MAS EJERCICIOS SOBRE TRIGGERS En esta seccion se pretende hacer algunos mas ejercicios de triggers para lograr que el amigo lector / estudiante adquiera las destrezas y comprenda el funcionamiento de los triggers en SQL Server.
6.1. UN TRIG GE R DE INSER CION PAR A S ACA R COPIA El siguiente trigger lo que busca es contar cuantos estudiantes se han ingresado que contengan el mismo numero de codigo en la tabla “estudiante” que el que se esta ingresando y este valor lo coloca en una tabla llamada “copia” que contiene las mismas columnas que la tabla estudiante mas una columna adicional llamada “numero_registros” que contiene el numero de registros que existen con este codigo en la tabla “estudiante”.
Esto es bastante util como por ejemplo para hacer una auditoria de cuantos registros existen de cada codigo en la tabla “estudiante” y tener una información actualizada en todo momento de este numero en la tabla “copia”. Para ello se crea primero la tabla “estudiante”:
create table estudiante ( codigo int, nombre nvarchar (50), salario int ) Luego la tabla “copia”:
create table copia ( codigo int, nombre nvarchar (50), salario int, numero_registros int ) Y se procede a crear el trigger: create trigger actual on estudiante for insert as declare @codigin int Enciclopedia Conociendo
Cuaderno de Triggers
35
Autor: Luis Felipe Wanumen Silva
declare @nombrecin nvarchar (50) declare @salarin int declare @numero_registros int declare @existe_en_copia int begin set @codigin = (select codigo from inserted) set @nombrecin= (select nombre from inserted) set @salarin = (select salario from inserted) set @numero_registros = (select count(*) from estudiante where codigo = @codigin) set @existe_en_copia = (select count(*) from estudiante where codigo = @codigin) if @existe_en_copia=1 insert into copia values(@codigin,@nombrecin,@salarin,1) else update copia set numero_registros = @existe_en_copia where codigo = @codigin end Observe amigo lector / estudiante que el trigger actua sobre la tabla “estudiante” y solo para las operaciones de inserción en mencionada tabla. De otra parte es bueno notar que los datos que inicialmente son ingresados en la tabla estudiante (Recuerde que primero se ejecuta la inserción y luego el trigger de insercion) son almacenados en tres variables declaradas globalmente que son: @codigin, @nombrecin y @salarin Las cuales son inicializadas al interior del trigger con los valores que se acaban de insertar en la tabla “estudiante”. De otra manera es bueno notar que se cuentan los registros que existen en la tabla “estudiante” que tienen el mismo codigo del que se acaba de ingresar
(recuerde que se incluye el que se acabo de ingresar), por esta razon es que si no existe dicho registro en la tabla la variabla “@existe_en_copia” vale “1” si el registro no existia,
porque quiere decir que fue el que se acabo de ingresar y como no existia en total existe una sola vez(pues se acaba de ingresar). En el caso que no exista se debe hacer una inserción en la tabla copia con el valor de “1” en el campo “numero_registros”, pero si ya existia, con seguridad ya habia un registro en la tabla “copia” (esto se presupone porque se parte del
supuesto que el programador ha creado al trigger antes de hacer incluso la primera inserción en la tabla “estdudiante”). En este ultimo caso, la variable “@existe_en_copia” contiene el numero de veces que existe este registro en la tabla “estudiante”, con lo cual este valor sirve para hacer la actualizacion en la tabla “copia”.
Enciclopedia Conociendo
Cuaderno de Triggers
36
Autor: Luis Felipe Wanumen Silva
Ahora bien, para lograr comprender el mecanismo de funcionamiento del trigger, se hacen unos ejemplos en los cuales se usa Suponga que ahora se hacen las siguientes inserciones: insert into estudiante values (1, 'Hugo',98) insert into estudiante values (2, 'Paco',98) insert into estudiante values (3, 'Luis',98) Se produce en el analizador de consultas un resultado similar al siguiente: (1 row(s) affected) (1 row(s) affected) (1 row(s) affected) (1 row(s) affected) (1 row(s) affected) (1 row(s) affected) En el cual se puede observer que se han hecho 6 operaciones. Dichas operaciones son de inserción y corresponden a tres inserciones en la tabla “estudiante” y tres inserciones en la tabla “copia”. Al finalizar la ejecución de las consultas y de los triggers asociados con
dichas consultas las tablas contienen los siguientes datos: Tabla estudiante: Codigo nombre 1 Hugo 2 Paco 3 Luis
Salario 98 98 98
Tabla copia: codigo nombre 1 Hugo 2 Paco 3 Luis
salario 98 98 98
numero_registros 1 1 1
En el anterior trigger se puede apreciar que el numero de registros que aparece en ambas tablas es igual y esto es debido a que los datos insertados en la tabla estudiante eran datos de estudiantes con codigos que no existian. A continuación vamos a realizar otras inserciones para probar el funcionamiento del trigger anteriormente elaborado. Para ello, hacemos las siguientes inserciones: insert into estudiante values (1, 'Hugo',98) insert into estudiante values (2, 'Maria',98) insert into estudiante values (5, 'Juan',98) insert into estudiante values (6, 'Elizabeth',98) Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
37
Después de lo cual vemos que ahora los resultados no son iguales en ambas tablas, por cuanto las tablas tienen los siguientes datos ahora: Datos de la tabla “estudiante”:
codigo 1 2 3 1 2 5 6
nombre Hugo Paco Luis Hugo Maria Juan Elizabeth
salario 98 98 98 98 98 98 98
Datos de la tabla “copia”:
codigo 1 2 3 5 6
nombre Hugo Paco Luis Juan Elizabeth
salario 98 98 98 98 98
numero_registros 2 2 1 1 1
Las siguientes observaciones se hacen con el fin de explicar detalladamente los resultados anteriores. Observamos que cuando insertamos: insert into estudiante values (1, 'Hugo',98) ya existia este codigo en la tabla “estudiante” y por esto se actualiza el registro en la tabla
copia con lo cual queda en la tabla copia: codigo nombre salario numero_registros 1 Hugo 98 2 Observamos que cuando insertamos: insert into estudiante values (2, 'Maria',98) ya existia este codigo en la tabla “estudiante”, pero a diferencia de la anterior actualizacion el campo “nombre” en la tabla copia no queda con el valor adecuado debido a que el trigger
solamente tiene en cuenta el campo codigo y no el campo nombre. Observe amigo lector /
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
38
estudiante que el campo nombre no es actualizado en la tabla copia y por tanto el registro en la tabla copia queda asi:
codigo 2
nombre Paco
salario 98
numero_registros 2
Observamos que cuando insertamos: insert into estudiante values (5, 'Juan',98) insert into estudiante values (6, 'Elizabeth',98)
No existian dichos registro en la tabla “estudiante” y por ello se insertan completamente en la tabla “copia” y de paso se les asigna el valor de “1” en el campo “numero_registros” en
mencionada tabla. Es decir se insertan los siguientes datos en la tabla “copia”:
codigo 5 6
nombre Juan Elizabeth
salario 98 98
numero_registros 1 1
Y dado que habia un registro inicialmente en la tabla “estudiante” que no ha sido afectado por ninguna inserción u actualizacion en la tabla “copia”, es bien claro que los registros que deben existir al final de las inserciones en la tabla “copia” deben ser:
codigo 1 2 3 5 6
nombre Hugo Paco Luis Juan Elizabeth
salario 98 98 98 98 98
numero_registros 2 2 1 1 1
6.2. TRIGG ER S PR OPUES TOS PAR A HACER DE TALLER EN CLASE Con el animo de afianzar los conocimientos adquiridos hasta el momento sobre triggers, se proponen los siguientes triggers para que el amigo lector / estudiante realice a fin de comprender bien el tema de triggers de inserción y de actualizacion. 1. Hacer un trigger que impida que se inserten estudiantes que tengan el mismo nombre
Enciclopedia Conociendo
Cuaderno de Triggers
39
Autor: Luis Felipe Wanumen Silva
2. Hacer un trigger que impida que se actualice un registro de un estudiante en su parte codigo de tal forma que dicho codigo quede duplicado en la base de datos 3. Hacer un trigger que impida que se inserte un estudiante con slario mayor a 900 4. Hacer un trigger que impida que se inserte un estudiante con codigo impar 5. Hacer un trigger que impida que se elimine un estudiante que este en la base de datos tres veces, pero si esta una, dos o mas de tres veces si lo deje eliminar. 6. Hacer un trigger que impida que se actualice el nombre de un estudiante, pero si desea actualizar el codigo o el salario o ambos si lo deje actualizar
6.3. UN TR IGG ER S INTER R ES ANTE DE ACTUALIZACION En este ejemplo se pretende hacer un trigger que cuando el usuario intente hacer una actualizacion sobre la tabla estudiante, se permita hacer dicha actualización si el nombre del estudiante por el que se desea actualizar es un nombre que no se encuentra en la lista de estudiantes. En el caso que se desee actualizar el nombre de los estudiantes por uno nombre cuyo contenido ya exista, no se debe permitir dicha actualizacion.
6.4. TRIGG ER TALLER PR OPUES TO COMO ME J ORA A L ANTER IOR Se propone al amigo lector / estudiante mejorar el trigger anterior validando que el usuario no pueda actualizar el código. Esto debido a que el trigger anterior supone que no se realizan actualizaciones sobre el campo código
Enciclopedia Conociendo
Cuaderno de Triggers
40
Autor: Luis Felipe Wanumen Silva
7. QUINTA UNIDAD: PROCEDIMIENTOS SOBRE TRIGGERS Para los ejercicios de esta unidad vamos a suponer la tabla “CONTINENTE”:
Id 1 2 3 4 5
Nombre AFRICA EUROPA ASIA OCEANIA RUSIA
planeta TIERRA TIERRA TIERRA TIERRA TIERRA
La cual tiene el siguiente trigger asociado: create trigger inserta_continente on CONTINENTE for insert as if (select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 OR (select count(*) from CONTINENTE, inserted where CONTINENTE.planeta like inserted.planeta )=1 OR ((select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 AND (select count(*) from CONTINENTE, inserted where CONTINENTE.planeta like inserted.planeta )=1) begin print "Ya existe este continente:" print "O planeta no existe" select * from inserted print "Por tanto los datos no se insertaron:" rollback transaction select * from CONTINENTE end
Enciclopedia Conociendo
Cuaderno de Triggers
41
Autor: Luis Felipe Wanumen Silva
7.1. VER DEPE NDENCIAS DE UN TRIG GE R El procedimiento almacenado sp_depends permite ver las dependencias de un trigger A manera de ejemplo observemos la siguiente instrucción: sp_depends inserta_continente el cual arroja los siguientes resultados: En la base de datos actual, el objeto especificado hace las siguientes referencias:
name dbo.CONTINENTE dbo.CONTINENTE dbo.CONTINENTE
type user table user table user table
updated no no no
selected yes yes yes
column id nombre planeta
7.2. VER EL TEXTO DE UN TR IGG ER El procedimiento almacenado sp_helptext permite ver el texto de un trigger La instrucción: sp_helptext inserta_continente produce el siguiente resultado: Text create trigger inserta_continente on CONTINENTE for insert as if (select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 OR (select count(*) Enciclopedia Conociendo
Cuaderno de Triggers
42
Autor: Luis Felipe Wanumen Silva
from CONTINENTE, inserted where CONTINENTE.planeta like inserted.planeta )=1 OR ((select count(*) from CONTINENTE, inserted where CONTINENTE.nombre like inserted.nombre)=2 AND (select count(*) from CONTINENTE, inserted where CONTINENTE.planeta like inserted.planeta )=1) begin print "Ya existe este continente:" print "O planeta no existe" select * from inserted print "Por tanto los datos no se insertaron:" rollback transaction select * from CONTINENTE end
7.3. VER LOS TRIG GE RS EXISTENTES EN UNA TABLA El procedimiento almacenado sp_helptrigger Permite listar los triggers existentes para una determinada tabla. La siguiente instrucción: sp_helptrigger CONTINENTE Produce el siguiente resultado:
trigger_name trigger_owner isupdate isdelete isinsert inserta_continente dbo 0 0 1 (1 filas afectadas)
7.4. VER INFORMACIÓN DE UN TRIGG ER El procedimiento almacenado: Enciclopedia Conociendo
Cuaderno de Triggers
43
Autor: Luis Felipe Wanumen Silva
sp_help permite mostrar ayuda sobre cualquier objeto en la base de datos, que se encuentre específicamente en la tabla sysobjects. Por ejemplo, la siguiente instrucción: sp_help inserta_continente Muestra información sobre el procedimiento almacenado “inserta_continente”, es
decir,muestra los siguientes resultados:
Name Owner Type Created_datetime inserta_continente dbo trigger 2003-08-25 18:08:23.570
7.5. VER TRIG GE R S CON LA TAB LA SYS OBJ ECTS Recordemos que en la tabla “sysobjects” en SQL Server, se almacena información sobre los
diversos objetos existentes en la base de datos, por lo cual es interesante observar que cuando se crea un trigger, se agrega un registro a dicha tabla y se identifica con el nombre “Tr” en la columna “Type” para indicar que se trata de un trigger. Por lo tanto la siguiente consulta: select * from sysobjects where name like 'inserta_continente' and type like 'Tr' Muestra información sobre un trigger denominado “inserta_continente”. Es decir, produce
el siguiente resultado(El resultado es mostrado transpuesto, debido a que produce demasiadas columnas y una sola fila, fue necesario transponer el resultado para mostrarlo en forma vertical):
Nombre Columna Valor
name
inserta_continente
Id xtype
530100929 TR
uid
1
info
0
status
1040
base_schema_ver
0
replinfo
0
parent_obj crdate
498100815 2003-08-25 18:08:23.570
Enciclopedia Conociendo
Cuaderno de Triggers
44
Autor: Luis Felipe Wanumen Silva ftcatid
0
schema_ver
0
stats_schema_ver
0
type
TR
userstat
0
sysstat
8
indexdel
0
refdate
2003-08-25 18:08:23.570
version
0
deltrig
498100815
instrig
0
updtrig
0
seltrig
0
category
0
cache
0
(1 filas afectadas)
Enciclopedia Conociendo
Cuaderno de Triggers
45
Autor: Luis Felipe Wanumen Silva
8. TRIGGERS QUE USAN FUNCIONES El uso de triggers, requiere en muchas ocasiones hacer uso de funciones con el fin de resolver ciertas tareas. Es por ello, que se plantea una sección como esta en la que se desarrollan algunos triggers que usan funciones de Transact SQL.
8.1. TR IGG ER QUE C ALC ULA LA LONGUTID DE UN CAMPO Suponga que se crea una tabla ¨ciudadano¨ tal como se muestra a continuación: create table ciudadano( codigo int, nombre nvarchar (50), longitud_del_nombre nvarchar (2) ) Se pretende en este ejercicio que cuando se hagan inserciones como las siguientes: insert into ciudadano(codigo, nombre) values(1,'Luis Felipe') se inserte el siguiente dato:
codigo 1
nombre Luis Felipe
longitud_del_nombre 11
En pocas palabras se quiere que el trigger inserte los datos del codigo y del nombre del ciudadano que el usuario ha solicitado, pero que el valor que se inserte en el campo “longitud_del_nombre” sea un valor calculado por el trigger e insertado por el mismo con el animo de disminuir la probabilidad de error al hacer inserciones manuales. Observe amigo lector / estudiante que el campo “longitud_del_nombre” contiene un numero que representa el numero de caracteres que contiene el campo “nombre”
(incluyendo espacios intermedios) El trigger que realiza la tarea antes expuesta es el siguiente: create trigger triggeractualizacionciudadano on ciudadano for insert as declare @codigin1 as int declare @nombrecin1 as nvarchar (50) Enciclopedia Conociendo
Cuaderno de Triggers
46
Autor: Luis Felipe Wanumen Silva
declare @longitudin1 as int begin select @codigin1 = (select codigo from inserted) select @nombrecin1 = (select nombre from inserted) select @longitudin1 = (select len(@nombrecin1)) rollback transaction if @longitudin1< 50 insert into ciudadano values(@codigin1,@nombrecin1,@longitudin1) end Recordemos que la tabla “inserted” contiene los datos que el usuario desea insertar en la consulta de inserción SQL. La variable “@nombrecin1” contiene la cadena del nombre que se desea insertar. Dicha cadena es pasada a la funcion “len()” para que esta funcion calcula
el numero de caracteres que contiene la cadena y este campo es insertado con la tabla “ciudadano”. Es bueno fijarse que en el caso que el campo tenga una longitud mas grande o
igual a 50 no se hacern inserciones. De todas formas es bueno comprender que si el campo tiene 50 caracteres, el motor de bases de datos permite la inserción porque este es el maximo numero de caracteres que se pueden ingresar en el campo “nombre” de la tabla
ciudadano, pero si el nombre tiene mas de 50 caracteres, antes que el trigger se ejecute ya el motor ha generado un error similar al siguiente: Servidor: mensaje 8152, nivel 16, estado 4, línea 1 Los datos de cadena o binarios se truncarían. Se terminó la instrucción. Es bueno hacer la siguiente aclaracion que si intentan ejecutar sentencias de inserción como las siguientes: insert into ciudadano values(2,'Luis Felipe Wanumen Silva',34) insert into ciudadano values(3,'Ana Esmeralda',30) se tienen los siguientes datos en la tabla “ciuda dano”:
codigo 1 2 3
Nombre Luis Felipe Luis Felipe Wanumen Silva Ana Esmeralda
longitud_del_nombre 11 25 13
Con lo cual queda bien claro que si intentan ingresar explícitamente datos en el campo “longitud_del_nombre”, dichos datos son ignorados, por cuanto los datos que son ingresados en este campo son los que el trigger calcula.
Enciclopedia Conociendo
Cuaderno de Triggers
47
Autor: Luis Felipe Wanumen Silva
8.2. TRIGG ER QUE INGR ES A NOMB R ES EN MINUSC ULA En algunos casos es interesante observar que si se tiene una tabla como la siguiente: create table ciudadano( codigo int, nombre nvarchar (50) ) Y se hacen las siguientes inserciones: insert into ciudadano values(1,'Luis Felipe') insert into ciudadano values(2,'Ana Esmeralda') insert into ciudadano values(3,'Claudia Bahamon') insert into ciudadano values(4,'Carolina Cruz') insert into ciudadano values(5,'Carolina Sabino') se producen los siguientes resultados:
codigo 1 2 3 4 5
nombre Luis Felipe Ana Esmeralda Claudia Bahamon Carolina Cruz Carolina Sabino
Lo cual muestra que los nombres que son ingresados en el campo “nombre” de la tabla ciudadano, son los nombres que el usuario ha solicitado su ingreso tal como los ha escrito en la consulta SQL de inserción, teniendo en cuenta si los ingreso con mayúscula o con minúscula. Esto es bien importante de comprender, puesto que en esta sección nos proponemos realizar un trigger que sea capaz de insertar los mismos nombres, pero ahora todos en minúscula, sin importar si la sentencia de inserción por parte del usuario de la base de datos fue con nombres en mayúscula o en minúscula. El trigger que realiza el trabajo anteriormente descrito es el siguiente: create trigger triggeractualizacionciudadano on ciudadano for insert as declare @codigin1 as int declare @nombrecin1 as nvarchar (50) Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
48
declare @mayuscula_nombre as nvarchar (50) begin select @codigin1 = (select codigo from inserted) select @nombrecin1 = (select nombre from inserted) select @mayuscula_nombre = (select lower (@nombrecin1)) rollback transaction BEGIN TRANSACTION insert into ciudadano values(@codigin1,@mayuscula_nombre) commit end de tal forma que cuando se realiza la inserción siguiente: insert into ciudadano values(1,'Luis Felipe') se obtiene el resultado siguiente:
codigo 1
nombre luis felipe
Y si se ejecutan una a una las siguientes instrucciones: insert into ciudadano values(2,'Ana Esmeralda') insert into ciudadano values(3,'Claudia Bahamon') insert into ciudadano values(4,'Carolina Cruz') insert into ciudadano values(5,'Carolina Sabino') Se obtiene una tabla con los siguientes datos:
codigo 1 2 3 4 5
nombre luis felipe ana esmeralda claudia bahamon carolina cruz carolina sabino
En el cual se observa que sin importar si los nombres son ingresados en mayúscula o en minúscula, el trigger convierte todo a minúscula y los inserta en la tabla ciudadano. TAREA PROPUESTA Como tarea le queda al amigo lector / estudiante que explique la razón por la cual no se pueden ejecutar todas las sentencias de inserción al tiempo, porque de hacerse, solamente se efectuaría la inserción del primer registro y los demás no se harían. Es bueno que el Enciclopedia Conociendo
Cuaderno de Triggers
49
Autor: Luis Felipe Wanumen Silva
lector explique por que es necesario en este ejercicio ejecutar las inserciones una a una para que el trigger funcione.
8.3. TRIG GE R QUE INGR ES A NOMBR ES EN MAYUSCULA Supongamos la tabla siguiente: create table ciudadano( codigo int, nombre nvarchar (50) ) Si se realizaren las siguientes inserciones: insert into ciudadano values(1,'Luis Felipe') insert into ciudadano values(2,'Ana Esmeralda') insert into ciudadano values(3,'Claudia Bahamon') insert into ciudadano values(4,'Carolina Cruz') insert into ciudadano values(5,'Carolina Sabino') se tendrian los siguientes resultados:
codigo 1 2 3 4 5
Nombre Luis Felipe Ana Esmeralda Claudia Bahamon Carolina Cruz Carolina Sabino
Pero lo que se pretende en esta seccion es que cuando se hagan inserciones, dichos nombres ingresados, sean pasados a mayusculas antes de ser insertados definitivamente a la tabla. Esto se puede mediante el siguiente trigger: create trigger triggeractualizacionciudadano on ciudadano for insert as declare @codigin1 as int declare @nombrecin1 as nvarchar (50) declare @minuscula_nombre as nvarchar (50) begin Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
50
select @codigin1 = (select codigo from inserted) select @nombrecin1 = (select nombre from inserted) select @minuscula_nombre = (select upper (@nombrecin1)) rollback transaction BEGIN TRANSACTION insert into ciudadano values(@codigin1,@minuscula_nombre) commit end de tal forma que cuando se realiza la inserción siguiente: insert into ciudadano values(1,'Luis Felipe') se obtiene el resultado siguiente:
codigo 1
nombre Luis Felipe
Y si se ejecutan una a una las siguientes instrucciones: insert into ciudadano values(2,'Ana Esmeralda') insert into ciudadano values(3,'Claudia Bahamon') insert into ciudadano values(4,'Carolina Cruz') insert into ciudadano values(5,'Carolina Sabino') Se obtiene una tabla con los siguientes datos:
codigo 1 2 3 4 5
nombre LUIS FELIPE ANA ESMERALDA CLAUDIA BAHAMON CAROLINA CRUZ CAROLINA SABINO
En el cual se observa que sin importar si los nombres son ingresados en mayúscula o en minúscula, el trigger convierte todo a mayúscula y los inserta en la tabla ciudadano. TAREA PROPUESTA Como tarea le queda al amigo lector / estudiante que explique la razón por la cual no se pueden ejecutar todas las sentencias de inserción al tiempo, porque de hacerse, solamente se efectuaría la inserción del primer registro y los demás no se harían. Es bueno que el lector explique por que es necesario en este ejercicio ejecutar las inserciones una a una para que el trigger funcione. Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
51
8.4. TRIGG ER QUE INGRE SA NOMBR ES AL RE VES Supongamos la tabla siguiente: create table ciudadano( codigo int, nombre nvarchar (50) ) El siguiente trigger: create trigger triggeractualizacionciudadano on ciudadano for insert as declare @codigin1 as int declare @nombrecin1 as nvarchar (50) declare @nombre_invertido as nvarchar (50) begin select @codigin1 = (select codigo from inserted) select @nombrecin1 = (select nombre from inserted) select @nombre_invertido = (select reverse(@nombrecin1)) rollback transaction BEGIN TRANSACTION insert into ciudadano values(@codigin1,@nombre_invertido) commit end hace que cuando se realicen las siguientes inserciones: insert into ciudadano values(1,'Luis Felipe') insert into ciudadano values(2,'Ana Esmeralda') insert into ciudadano values(3,'Claudia Bahamon') insert into ciudadano values(4,'Carolina Cruz') insert into ciudadano values(5,'Carolina Sabino') se produzcan los siguientes resultados:
codigo 1 2 3
nombre epileF siuL adlaremsE anA nomahaB aidualC
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
4 5
52
zurC aniloraC onibaS aniloraC
8.5. TALLE R INVE S TIG ATIVO Con los anteriores ejercicios fue claro que se pueden usar una serie de funciones para hacer que los triggers hagan ciertas cosas, bien sea ante una inserción, una actualización o una eliminación. En esta sección se deja al amigo lector / estudiante que estudie cada una de las siguientes funciones y sobre cada una de ellas realice triggers de inserción, de actualización y de eliminación a fin que logre comprender no solamente el funcionamiento de los triggers, sino también de las funciones investigadas. 1. Hacer un trigger de actualización en el que se explique el funcionamiento de Asin 2. Hacer un trigger de eliminación en el que se explique el funcionamiento de Asin 3. Hacer un trigger de inserción en el que se explique el funcionamiento de Asin 4. Hacer un trigger de actualización en el que se explique el funcionamiento de Atan 5. Hacer un trigger de eliminación en el que se explique el funcionamiento de Atan 6. Hacer un trigger de inserción en el que se explique el funcionamiento de Atan 7. Hacer un trigger de actualización en el que se explique el funcionamiento de Acos 8. Hacer un trigger de eliminación en el que se explique el funcionamiento de Acos 9. Hacer un trigger de inserción en el que se explique el funcionamiento de Acos 10. Hacer un trigger de actualización en el que se explique el funcionamiento de Patindex 11. Hacer un trigger de eliminación en el que se explique el funcionamiento de Patindex 12. Hacer un trigger de inserción en el que se explique el funcionamiento de Patindex 13. Hacer un trigger de actualización en el que se explique el funcionamiento de Replicate 14. Hacer un trigger de eliminación en el que se explique el funcionamiento de Replicate 15. Hacer un trigger de inserción en el que se explique el funcionamiento de Replicate 16. Hacer un trigger de actualización en el que se explique el funcionamiento de Replace 17. Hacer un trigger de eliminación en el que se explique el funcionamiento de Replace 18. Hacer un trigger de inserción en el que se explique el funcionamiento de Replace 19. Hacer un trigger de actualización en el que se explique el funcionamiento de Rtrim 20. Hacer un trigger de eliminación en el que se explique el funcionamiento de Rtrim 21. Hacer un trigger de inserción en el que se explique el funcionamiento de Rtrim 22. Hacer un trigger de actualización en el que se explique el funcionamiento de Ltrim 23. Hacer un trigger de eliminación en el que se explique el funcionamiento de Ltrim 24. Hacer un trigger de inserción en el que se explique el funcionamiento de Ltrim 25. Hacer un trigger de actualización en el que se explique el funcionamiento de Right 26. Hacer un trigger de eliminación en el que se explique el funcionamiento de Right 27. Hacer un trigger de inserción en el que se explique el funcionamiento de Right 28. Hacer un trigger de actualización en el que se explique el funcionamiento de Left Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
53
29. Hacer un trigger de eliminación en el que se explique el funcionamiento de Left 30. Hacer un trigger de inserción en el que se explique el funcionamiento de Left 31. Hacer un trigger de actualización en el que se explique el funcionamiento de Convert 32. Hacer un trigger de eliminación en el que se explique el funcionamiento de Convert 33. Hacer un trigger de inserción en el que se explique el funcionamiento de Convert 34. Hacer un trigger de actualización en el que se explique el funcionamiento de Charindex 35. Hacer un trigger de eliminación en el que se explique el funcionamiento de Charindex 36. Hacer un trigger de inserción en el que se explique el funcionamiento de Charindex 37. Hacer un trigger de actualización en el que se explique el funcionamiento de Ascii 38. Hacer un trigger de eliminación en el que se explique el funcionamiento de Ascii 39. Hacer un trigger de inserción en el que se explique el funcionamiento de Ascii 40. Hacer un trigger de actualización en el que se explique el funcionamiento de Substring 41. Hacer un trigger de eliminación en el que se explique el funcionamiento de Substring 42. Hacer un trigger de inserción en el que se explique el funcionamiento de Substring
Enciclopedia Conociendo
Cuaderno de Triggers
54
Autor: Luis Felipe Wanumen Silva
9. ESPECIFICAR CUANDO ACTIVAR LOS TRIGGERS En las secciones anteriores se ha visto que los triggers se ejecutan por defecto después de realizada la operación de inserción, actualización o eliminación, pero la verdad este comportamiento se puede cambiar y en el momento de creación del trigger se puede especificar en que momento se desea que se ejecute el trigger.
9.1. TRIG GE R CON ESPE CIFICADOR FOR Supongamos que tenemos la siguiente tabla: create table ciudadano( codigo int, nombre nvarchar (50) ) Y que definimos el siguiente trigger: create trigger triggeractualizacionciudadano on ciudadano for insert as declare @numero_registros as int begin print 'Los registros que hay son ' select @numero_registros = (select count(*) from ciudadano) print @numero_registros select * from ciudadano end Observamos que cuando ejecutamos el trigger se produce Los registros que hay son 1 Codigo nombre 4 Carolina Cruz Lo cual nos deja bien en claro que el trigger se esta ejecutando después de ejecutada la sentencia de inser ción. Este comportamiento se presenta por la palabra “for”, la cual le indica al motor de bases de datos que ejecute el trigger después de realizada la sentencia de inserción.
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
55
9.2. TRIG GE R CON ESPE CIFICADOR A FTER Supongamos que tenemos la siguiente tabla: create table ciudadano( codigo int, nombre nvarchar (50) ) Y que definimos el siguiente trigger: create trigger triggeractualizacionciudadano on ciudadano after insert as declare @numero_registros as int begin print 'Los registros que hay son ' select @numero_registros = (select count(*) from ciudadano) print @numero_registros select * from ciudadano end Observamos que cuando ejecutamos el trigger se produce Los registros que hay son 1 Codigo nombre 4 Carolina Cruz Lo cual nos deja comprender que al igual que el trigger de la sección anterior, este trigger se ejecuta después de ejecutadas las instrucciones de inserción. En pocas términos podemos afirmar que tanto las palabras “for’ como “after” le indican al motor que ejecute el trigger después de ejecutadas las instrucciones. Este tipo de cosas es bueno tenerlas en cuenta. NOTA: El numero de triggers que una tabla puede tener del tipo “for’ o “after” pueden ser varios.
Bueno, eso si, teniendo en cuenta que un trigger de pronto no invoque a otro y entonces tendríamos una invocación infinita de triggers, lo que desembocaría en un colapso de la base de datos en un momento dado. Esta restricción también aplica no solo para tablas, sino para vistas. Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
56
9.3. TRIGG ER CON ES PEC IFICADOR INSTEAD OF Supongamos que tenemos la siguiente tabla: create table ciudadano( codigo int, nombre nvarchar (50) ) Y que definimos el siguiente trigger: create trigger triggeractualizacionciudadano on ciudadano instead of insert as declare @numero_registros as int begin print 'Los registros que hay son ' select @numero_registros = (select count(*) from ciudadano) print @numero_registros select * from ciudadano end Observamos que cuando ejecutamos el trigger se produce Los registros que hay son 0 Codigo Nombre Con lo cual podemos afirmar que cuando se ejecuto el trigger todavía no se había ejecutado la instrucción de inserción. NOTA: El numero de triggers que una tabla puede tener del tipo “instead of’ puede ser máximo de
uno por cada tipo de acción. (Las acciones pueden ser: update, inserted, deleted). Esta restricción también aplica no solo para tablas, sino para vistas.
Enciclopedia Conociendo
Cuaderno de Triggers
Autor: Luis Felipe Wanumen Silva
57
TABLA DE CONTENIDO 1. PRIMERA UNIDAD: INTRODUCCIÓN A LOS TRIGGERS ___________________ 3 1.1. DEFINICIÓN ______________________________________________________ 3 1.2. OBJETIVO DE LOS TRIGGERS _______________________________________ 3 1.3. CONSIDERACIONES SOBRE TRIGGERS ______________________________ 3 2. SEGUNDA UNIDAD: TRIGGERS Y MAS CONCEPTOS BASICOS _____________ 4 2.1. UN TRIGGER SIEMPRE SE BORRA CUANDO SE BORRA LA TABLA ASOCIADA ___________________________________________________________ 4 2.2. UN TRIGGER DEBE SER LA PRIMERA SENTENCIA EN ARCHIVO DE LOTES _______________________________________________________________ 5 2.3. MENSAJES EN LA INSERCIÓN ______________________________________ 7 2.4. MENSAJES EN LA ACTUALIZACIÓN _________________________________ 8 2.5. MENSAJES EN EL BORRADO DE REGISTROS _________________________ 9 3. TERCERA UNIDAD: TRIGGERS DE INSERCIÓN __________________________ 11 3.1. LA TABLA INSERTED _____________________________________________ 11 3.2. INSERCIÓN CONDICIONAL ________________________________________ 13 3.3. ORDEN DE EJECUCIÓN CON TRIGGERS ____________________________ 15 3.4. IMPIDIENDO VALORES NO EXISTENTES ___________________________ 16 4. CUARTA UNIDAD: TRIGGERS DE ACTUALIZACIÓN _____________________ 21 4.1. CONCEPTUALMENTE COMPRENDIENDO LOS TRIGGERS DE ACTUALIZACION ____________________________________________________ 21 4.2. COLUMNAS ACTUALIZADAS EN UNA ACTUALIZACIÓN _____________ 22 4.3. TAREA DE INVESTIGACIÓN _______________________________________ 24 4.4. TRIGGER DE ACTUALIZACIÓN CONFUSO __________________________ 24 5. PRIMEROS EJERCICIOS SOBRE TRIGGERS______________________________ 27 5.1. UN PRIMER EJERCICIO SOBRE TRIGGERS DE ACTUALIZACION ______ 27 5.2. UN SEGUNDO EJERCICIO DE TRIGGERS ____________________________ 29 5.3. UN TERCER EJERCICIO DE TRIGGERS ______________________________ 31 6. MAS EJERCICIOS SOBRE TRIGGERS ___________________________________ 34 6.1. UN TRIGGER DE INSERCION PARA SACAR COPIA ___________________ 34 6.2. TRIGGERS PROPUESTOS PARA HACER DE TALLER EN CLASE ________ 38 6.3. UN TRIGGERS INTERRESANTE DE ACTUALIZACION ________________ 39 6.4. TRIGGER TALLER PROPUESTO COMO MEJORA AL ANTERIOR _______ 39 7. QUINTA UNIDAD: PROCEDIMIENTOS SOBRE TRIGGERS _________________ 40 7.1. VER DEPENDENCIAS DE UN TRIGGER______________________________ 41 7.2. VER EL TEXTO DE UN TRIGGER ___________________________________ 41 7.3. VER LOS TRIGGERS EXISTENTES EN UNA TABLA ___________________ 42 7.4. VER INFORMACIÓN DE UN TRIGGER_______________________________ 42 7.5. VER TRIGGERS CON LA TABLA SYSOBJECTS _______________________ 43 8. TRIGGERS QUE USAN FUNCIONES ____________________________________ 45 8.1. TRIGGER QUE CALCULA LA LONGUTID DE UN CAMPO _____________ 45 Enciclopedia Conociendo
Cuaderno de Triggers