Documente Academic
Documente Profesional
Documente Cultură
Una vez que hemos visto la teoria básica de disparadores nada mejor que unos cuantos
ejemplos prácticos para ver como se usan y defininen los disparadores en PostgreSQL.
(estos ejemplos han sido comprobados en postgreSQL 8.3.7).
postgres@server:~$ psql
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
postgres=# \c test001
You are now connected to database "test001".
test001=#
Ahora creamos una tabla para poder definir nuestro primer disparador:
Después tenemos que crear una función en PL/pgSQL para ser usada por nuestro
disparador. Nuestra primera función es la más simple que se puede definir y lo único que
hará será devolver el valor NULL:
--
-- Esta funcion es usada para proteger datos en un tabla
-- No se permitira el borrado de filas si la usamos
-- en un disparador de tipo BEFORE / row-level
--
RETURN NULL;
END;
$proteger_datos$ LANGUAGE plpgsql;
test001=# \d numeros
Table "public.numeros"
Column | Type | Modifiers
----------+--------+-----------
numero | bigint | not null
cuadrado | bigint |
cubo | bigint |
raiz2 | real |
raiz3 | real |
Indexes:
"numeros_pkey" PRIMARY KEY, btree (numero)
Triggers:
proteger_datos BEFORE DELETE ON numeros
FOR EACH ROW EXECUTE PROCEDURE proteger_datos()
Ahora vamos a definir una nueva función un poco más complicada y un nuevo disparador
en nuestra tabla numeros:
NEW.cuadrado := power(NEW.numero,2);
NEW.cubo := power(NEW.numero,3);
NEW.raiz2 := sqrt(NEW.numero);
NEW.raiz3 := cbrt(NEW.numero);
RETURN NEW;
END;
$rellenar_datos$ LANGUAGE plpgsql;
CREATE TRIGGER rellenar_datos BEFORE INSERT OR UPDATE
ON numeros FOR EACH ROW
EXECUTE PROCEDURE rellenar_datos();
test001=# \d numeros
Table "public.numeros"
Column | Type | Modifiers
----------+--------+-----------
numero | bigint | not null
cuadrado | bigint |
cubo | bigint |
raiz2 | real |
raiz3 | real |
Indexes:
"numeros_pkey" PRIMARY KEY, btree (numero)
Triggers:
proteger_datos BEFORE DELETE ON numeros
FOR EACH ROW EXECUTE PROCEDURE proteger_datos()
rellenar_datos BEFORE INSERT OR UPDATE ON numeros
FOR EACH ROW EXECUTE PROCEDURE rellenar_datos()
Hemos realizado 2 INSERT y 1 UPDATE. Esto significa que por cada uno de estos comandos
el sistema ha ejecutado la función rellenar_datos(), una vez por cada fila afectada y antes
de actualizar la tabla numeros.
Como podeis comprobar, nosotros solamente hemos actualizado la columna numero, pero
al listar el contenido de nuestra tabla vemos como el resto de columnas (cuadrado, cubo,
raiz2 y raiz3) tambien contienen valores.
NEW.cuadrado := power(NEW.numero,2);
NEW.cubo := power(NEW.numero,3);
NEW.raiz2 := sqrt(NEW.numero);
NEW.raiz3 := cbrt(NEW.numero);
RETURN NEW;
Esta función retorna el valor NULL y esto significa, segun la regla 6.1 definida en este
artículo, que para la fila afectada no se ejecutará el comanado DELETE. Por eso y mientras
este disparador este instalado será imposible de borrar nada de la tablanumeros.
Vamos a continuar complicando las cosas. Primero, vamos a desinstalar nuestros dos
disparadores proteger_datos y rellenar_datos.
NEW.cuadrado := power(NEW.numero,2);
NEW.cubo := power(NEW.numero,3);
NEW.raiz2 := sqrt(NEW.numero);
NEW.raiz3 := cbrt(NEW.numero);
RETURN NEW;
END IF;
END;
$proteger_y_rellenar_datos$ LANGUAGE plpgsql;
CREATE TRIGGER proteger_y_rellenar_datos BEFORE INSERT OR UPDATE OR DELETE
ON numeros FOR EACH ROW
EXECUTE PROCEDURE proteger_y_rellenar_datos();
test001=# \d numeros
Table "public.numeros"
Column | Type | Modifiers
----------+--------+-----------
numero | bigint | not null
cuadrado | bigint |
cubo | bigint |
raiz2 | real |
raiz3 | real |
Indexes:
"numeros_pkey" PRIMARY KEY, btree (numero)
Triggers:
rellenar_datos BEFORE INSERT OR DELETE OR UPDATE ON numeros
FOR EACH ROW EXECUTE PROCEDURE proteger_y_rellenar_datos()
Y todo seguirá funcionando de la misma manera que con los dos disparadores del
comienzo:
Por último y antes de terminar, vamos a definir un disparador del tipo statement-level que
se ejecute despues de nuestras sentencias INSERT, UPDATE y DELETE. La función
ejecutada por este disparador grabará datos de la ejecución en la tabla cambios (esto no
sirve para mucho en la vida real, pero como ejemplo esta bien para que veais como
funciona)
Para demostrar como podemos utilizar esto vamos a definir una nueva tabla:
RETURN NULL;
END;
$grabar_operaciones$ LANGUAGE plpgsql;
Y el disparador lo instalariamos de la siguiente forma:
test001=# \d numeros;
Table "public.numeros"
Column | Type | Modifiers
----------+--------+-----------
numero | bigint | not null
cuadrado | bigint |
cubo | bigint |
raiz2 | real |
raiz3 | real |
Indexes:
"numeros_pkey" PRIMARY KEY, btree (numero)
Triggers:
grabar_operaciones AFTER INSERT OR DELETE OR UPDATE ON numeros
FOR EACH STATEMENT EXECUTE PROCEDURE grabar_operaciones()
proteger_y_rellenar_datos BEFORE INSERT OR DELETE OR UPDATE ON numeros
FOR EACH ROW EXECUTE PROCEDURE proteger_y_rellenar_datos()
Y con este último ejemplo, terminamos este artículo sobre disparadores en PostgreSQL.
Solamente os queda practicar, leer la documentación y usar vuestra imaginación.
Para instalar un trigger tienes que hacer dos cosas:
Ya contaras.
La Función
CREATE OR REPLACE FUNCTION permitir_insert() RETURNS
trigger AS
BEGIN
En el cuerpo de la función, preguntamos qué tabla y campo voy a
consultar para verificar si permito o no el insert, esto se hace
consultando a TG_ARGV:
tabla := TG_ARGV[0];
campo := TG_ARGV[1];
¿sencillo, no?.
La consulta
Ahora, debo verificar que en el campo “campo”::text de la tabla
tabla::text (variable) existe un valor con lo que está contenido
en “vrecord”, de lo contrario retorno NULL y el trigger no se
ejecuta (la operación de inserción no ocurre).
IF rc = 0 THEN
RAISE EXCEPTION 'no existe el valor % en el campo
% de la tabla %', vrecord, campo, tabla;
RETURN NULL;
END IF;
RETURN NEW;
END;
LANGUAGE plpgsql VOLATILE
Y ya podemos usarla.
Conclusiones
Sé que parece muy “rebuscado” un ejemplo que bien podría salir
con una clave foránea, pero sirve para el hecho de demostrar la
posibilidad de obtener e iterar sobre el objeto NEW, consultar
metadata al “information_schema” o realizar cualquier
operación de manera dinámica, pasando parámetros y
consultando las variables mágicas TG_* de postgreSQL.
Ejemplos de Trigger
Tenemos una tabla de archivos (llamada archivos), en la que uno de los campos
es un MD5 sobre el contenido del archivo (campo llamado dmd5). En una tabla
anexa (llamada aux), necesitamos llevar la cuenta de cuántos registros tenemos
con el mismo MD5, para esto tenemos dos campos: dmd5 y cont. Así, cada vez
que insertamos un registro en archivos debemos de actualizar el contador
correspondiente en aux. De igual manera, cuando borramos un registro
de archivos, deberemos de decrementar el contador correspondiente en aux y en
caso de que sea el único, eliminar el registro.
Los DROPs antes de crear la función y el trigger, son para garantizar que las
funciones no existen previamente y sobre todo, porque tuve que hacer un
demonial de pruebas antes de que la cosa jalara.
Recordemos que los triggers no pueden recibir argumentos y siempre tienen que
regresar un valor opaco.
Eliminamos el trigger para, en caso de que ya exista uno con el mismo nombre,
garantizar que se ejecutará el que vamos a declarar. Como podemos ver,
los triggers están asociados a tablas y por eso se debe de indicar de que tabla lo
eliminamos al momento de efectuar el drop.
Ahora veamos el caso en que se eliminan registros. Por supuesto, en este caso
estamos eliminando un registro que está en la base, así que no necesitamos ver
primero si existe. El único considerando a tomar en cuenta es el caso en que
el MD5 es único, en cuyo caso habremos de eliminar de la tabla dmd5 el registro.
DROP FUNCTION dec_aux ();
CREATE FUNCTION dec_aux () RETURNS OPAQUE AS '
BEGIN
UPDATE aux SET cont=cont-1 WHERE dmd5 = OLD.dmd5;
DELETE FROM aux where cont < 1;
RETURN NULL;
END;
' LANGUAGE 'plpgsql';
DROP TRIGGER del_arc ON archivos;
CREATE TRIGGER del_arc AFTER DELETE ON archivos
FOR EACH ROW EXECUTE PROCEDURE dec_aux();
Creamos el trigger:
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
Y vemos un ejemplo:
mancha=> insert into emp values ('Meiga Pintita', 2500);
INSERT 11635171 1
mancha=> insert into emp values ('Misha Negrita', 2500);
INSERT 11635172 1
mancha=> select * from emp;
nombre |salario|last_date |last_user
-------------+-------+----------------------------+---------
Bill Clinton | 1000|Mon 03 Jan 12:28:12 2000 CST|mancha
Boris Yeltsin| 2000|Mon 03 Jan 12:28:36 2000 CST|mancha
Meiga Pintita| 2500|Wed 05 Jan 13:07:12 2000 CST|mancha
Misha Negrita| 2500|Wed 05 Jan 13:08:37 2000 CST|mancha
(4 rows)
mancha=> insert into emp values ('Perico Cerillo', -1000000);
ERROR: Perico Cerillo no puede tener un salario negativo
mancha=> insert into emp values (NULL , 1000000);
ERROR: nombre no puede ser NULL
mancha=> insert into emp values ('Perico Cerillo', NULL);
ERROR: Perico Cerillo no puede tener un salario NULL