Sunteți pe pagina 1din 18

Ejemplos prácticos

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).

Creamos una base de datos para utilizarla con nuestros ejemplos:

postgres@server:~$ psql
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.

Type: \copyright for distribution terms


\h for help with SQL commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit

postgres=# CREATE DATABASE test001;


CREATE DATABASE

postgres=# \c test001
You are now connected to database "test001".

test001=#

Lo prímero que tenemos que hacer es instalar el lenguaje plpgsql si no lo tenemos


instalado.

CREATE PROCEDURAL LANGUAGE plpgsql;

Ahora creamos una tabla para poder definir nuestro primer disparador:

CREATE TABLE numeros(


numero bigint NOT NULL,
cuadrado bigint,
cubo bigint,
raiz2 real,
raiz3 real,
PRIMARY KEY (numero)
);

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:

CREATE OR REPLACE FUNCTION proteger_datos() RETURNS TRIGGER AS $proteger_datos$


DECLARE
BEGIN

--
-- 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;

A continuación definimos en la tabla numeros un disparador del tipo BEFORE / row-level


para la operación DELETE. Más adelante veremos como funciona:

CREATE TRIGGER proteger_datos BEFORE DELETE


ON numeros FOR EACH ROW
EXECUTE PROCEDURE proteger_datos();

La definición de nuestra tabla ha quedado asi:

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:

CREATE OR REPLACE FUNCTION rellenar_datos() RETURNS TRIGGER AS $rellenar_datos$


DECLARE
BEGIN

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();

La definición de nuestra tabla ha quedado asi:

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()

Ahora vamos a ver como los disparadores que hemos definido en la


tabla numerosfuncionan:

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+-------+-------
(0 rows)

test001=# INSERT INTO numeros (numero) VALUES (2);


INSERT 0 1

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
(1 rows)

test001=# INSERT INTO numeros (numero) VALUES (3);


INSERT 0 1

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
3 | 9 | 27 | 1.73205 | 1.44225
(2 rows)

test001=# UPDATE numeros SET numero = 4 WHERE numero = 3;


UPDATE 1

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
(2 rows)

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.

De esta actualización se ha encargado la función rellenar_datos() llamada por nuestro


disparador. Vamos a analizar lo que hace esta función:

NEW.cuadrado := power(NEW.numero,2);
NEW.cubo := power(NEW.numero,3);
NEW.raiz2 := sqrt(NEW.numero);
NEW.raiz3 := cbrt(NEW.numero);

RETURN NEW;

 Cuando ejecutamos el primer INSERT (numero = 2), el


disparador rellenar_datosllama a la función rellenar_datos() una vez.

 El valor de la variable NEW al empezar a ejecutarse rellenar_datos() es numero=2,


cuadrado=NULL, cubo=NULL, raiz2=NULL, raiz3=NULL.

 Nuestra tabla todavia no contiene ninguna fila.

 A continuación calculamos el cuadrado, el cubo, la raiz cuadrada y la raiz cubica de


2 y asignamos estos valores a NEW.cuadrado, NEW.cubo, NEW.raiz2 y NEW.raiz3.

 El valor de la variable NEW antes de la sentencia RETURN NEW es ahora numero=2,


cuadrado=4, cubo=8, raiz2=1.41421, raiz3=1.25992.

 Con la sentencia RETURN NEW, retornamos la fila (RECORD) almacenada en la


variable NEW, y salimos de la función rellenar_datos(). El sistema almacena
entonces el RECORD contenido en NEW en la tabla numeros

Como podeis ver, todo muy lógico.


De la misma manera funciona el disparador proteger_datos cuando ejecutamos una
sentencia DELETE. Antes de borrar nada ejecutará la función proteger_datos().

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.

test001=# DELETE FROM numeros;


DELETE 0

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
(2 rows)

Vamos a continuar complicando las cosas. Primero, vamos a desinstalar nuestros dos
disparadores proteger_datos y rellenar_datos.

test001=# DROP TRIGGER proteger_datos ON numeros;


DROP TRIGGER

test001=# DROP TRIGGER rellenar_datos ON numeros;


DROP TRIGGER

A continuación crearemos un disparador único para las sentencias INSERT, UPDATE y


DELETE. Este nuevo disparador utilizará una nueva función en la que tendremos que tener
en cuenta que tipo de comando ha activado el disparador, si queremos retornar el valor
correcto. Para ello utilizaremos la variable TG_OP.

CREATE OR REPLACE FUNCTION proteger_y_rellenar_datos() RETURNS TRIGGER AS $proteger_y_rellenar_datos$


DECLARE
BEGIN

IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE' ) THEN

NEW.cuadrado := power(NEW.numero,2);
NEW.cubo := power(NEW.numero,3);
NEW.raiz2 := sqrt(NEW.numero);
NEW.raiz3 := cbrt(NEW.numero);
RETURN NEW;

ELSEIF (TG_OP = 'DELETE') THEN


RETURN NULL;

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();

La definición de nuestra tabla ha quedado asi:

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:

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
(2 rows)

test001=# INSERT INTO numeros (numero) VALUES (5);


INSERT 0 1

test001=# INSERT INTO numeros (numero) VALUES (6);


INSERT 0 1

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
5 | 25 | 125 | 2.23607 | 1.70998
6 | 36 | 216 | 2.44949 | 1.81712
(4 rows)

test001=# UPDATE numeros SET numero = 10 WHERE numero = 6;


UPDATE 1

test001=# SELECT * from numeros ;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
5 | 25 | 125 | 2.23607 | 1.70998
10 | 100 | 1000 | 3.16228 | 2.15443
(4 rows)

test001=# DELETE FROM numeros where numero =10;


DELETE 0

test001=# SELECT * from numeros;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
5 | 25 | 125 | 2.23607 | 1.70998
10 | 100 | 1000 | 3.16228 | 2.15443
(4 rows)

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:

CREATE TABLE cambios(


timestamp_ TIMESTAMP WITH TIME ZONE default NOW(),
nombre_disparador text,
tipo_disparador text,
nivel_disparador text,
comando text
);

La función la podemos definir asi:

CREATE OR REPLACE FUNCTION grabar_operaciones() RETURNS TRIGGER AS $grabar_operaciones$


DECLARE
BEGIN

INSERT INTO cambios (


nombre_disparador,
tipo_disparador,
nivel_disparador,
comando)
VALUES (
TG_NAME,
TG_WHEN,
TG_LEVEL,
TG_OP
);

RETURN NULL;
END;
$grabar_operaciones$ LANGUAGE plpgsql;
Y el disparador lo instalariamos de la siguiente forma:

CREATE TRIGGER grabar_operaciones AFTER INSERT OR UPDATE OR DELETE


ON numeros FOR EACH STATEMENT
EXECUTE PROCEDURE grabar_operaciones();

La definición de nuestra tabla quedaria asi:

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()

A continuación podeis ver como funcionaria:

test001=# SELECT * from cambios ;


timestamp_ | nombre_disparador | tipo_disparador | nivel_disparador | comando
------------+-------------------+-----------------+------------------+---------
(0 rows)

test001=# INSERT INTO numeros (numero) VALUES (100);


INSERT 0 1

test001=# SELECT * from numeros ;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+---------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
5 | 25 | 125 | 2.23607 | 1.70998
10 | 100 | 1000 | 3.16228 | 2.15443
100 | 10000 | 1000000 | 10 | 4.64159
(5 rows)

test001=# SELECT * from cambios ;


timestamp_ | nombre_disparador | tipo_disparador | nivel_disparador | comando
-------------------------------+--------------------+-----------------+------------------+---------
2009-06-11 23:05:29.794534+02 | grabar_operaciones | AFTER | STATEMENT | INSERT
(1 row)

test001=# UPDATE numeros SET numero = 1000 WHERE numero = 100;


UPDATE 1
test001=# SELECT * from numeros ;
numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
5 | 25 | 125 | 2.23607 | 1.70998
10 | 100 | 1000 | 3.16228 | 2.15443
1000 | 1000000 | 1000000000 | 31.6228 | 10
(5 rows)

test001=# SELECT * from cambios ;


timestamp_ | nombre_disparador | tipo_disparador | nivel_disparador | comando
-------------------------------+--------------------+-----------------+------------------+---------
2009-06-11 23:05:29.794534+02 | grabar_operaciones | AFTER | STATEMENT | INSERT
2009-06-11 23:06:08.259421+02 | grabar_operaciones | AFTER | STATEMENT | UPDATE
(2 rows)

test001=# DELETE FROM numeros where numero =1000;


DELETE 0

test001=# SELECT * from numeros ;


numero | cuadrado | cubo | raiz2 | raiz3
--------+----------+------------+---------+---------
2 | 4 | 8 | 1.41421 | 1.25992
4 | 16 | 64 | 2 | 1.5874
5 | 25 | 125 | 2.23607 | 1.70998
10 | 100 | 1000 | 3.16228 | 2.15443
1000 | 1000000 | 1000000000 | 31.6228 | 10
(5 rows)

test001=# SELECT * from cambios ;


timestamp_ | nombre_disparador | tipo_disparador | nivel_disparador | comando
-------------------------------+--------------------+-----------------+------------------+---------
2009-06-11 23:05:29.794534+02 | grabar_operaciones | AFTER | STATEMENT | INSERT
2009-06-11 23:06:08.259421+02 | grabar_operaciones | AFTER | STATEMENT | UPDATE
2009-06-11 23:06:26.568632+02 | grabar_operaciones | AFTER | STATEMENT | DELETE
(3 rows)

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:

1. Crear una funcion para el trigger


2. Instalar el trigger en una/varias tablas

Podrias hacer algo asi:

CREATE OR REPLACE FUNCTION actualizar_nombre() RETURNS TRIGGER AS


$trigger_ejemplo$
BEGIN
NEW.nombre := NEW.pat_pad || ' ' || NEW.mat_pad || ' ' ||
NEW.nom_pad ;
RETURN NEW;
END;
$trigger_ejemplo$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_ejemplo


BEFORE INSERT OR UPDATE ON 'tu_tabla'
FOR EACH ROW EXECUTE PROCEDURE actualizar_nombre();

Ya contaras.

La Función
CREATE OR REPLACE FUNCTION permitir_insert() RETURNS
trigger AS

Comenzamos con la definición, la llamaremos “permitir_insert()”


y obviamente, retorna un trigger.

luego, en la sección de declaración, definimos las únicas


variables que vamos a necesitar para este ejemplo:
vrecord integer;
campo text;
tabla text;
id int;
rc int;

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];

Como ven, ya que podamos recibir cualquier cantidad de


parámetros en el trigger, las opciones son infinitas.

Ahora, necesito saber que hay en el campo trigger (NEW.campo),


en este caso, debo reemplazar “campo” con el nombre del
campo de la tabla que disparó el trigger (y que voy a consultar
en otra tabla), como en mi caso el valor de el “campo” en todas
las “tablas” que usarán esta función de trigger es entero, he
declarado “vrecord” integer, si su tipo será distinto, sería
conveniente declararlo “anyelement”.
EXECUTE 'SELECT ($1).' || quote_ident(campo) || '::text'
INTO vrecord USING NEW;
Lo que hace ese código, es ejecutar un $1.”campo”::text (dónde
campo es una variable) y mete su valor en la variable “vrecord”,
se usa el objeto NEW para la sustitución del $1.

Ejemplo, si el campo se llama “piezas“, ese “EXECUTE” quedaría


“NEW.piezas” y entonces metería el valor de NEW.piezas en la
variable “vrecord”.

¿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).

Sería algo como un “constraint” pero sin usar reglas de


constraint, ¿entendido?.

IF TG_OP = 'INSERT' THEN

EXECUTE format('SELECT '||quote_ident(campo)||' FROM


'||quote_ident(tabla)||' WHERE '||quote_ident(campo)||'
=$1') USING vrecord INTO id;

En este caso, he ejecutado un SELECT (SELECT “campo”::text


FROM “tabla”::text WHERE campo::text = vrecord), claro que
haciendo las conversiones y los reemplazos respectivos.

El valor de esa ejecución lo agrego a la variable id.


Si adicionalmente se desea averiguar si esa consulta anterior
retornó filas, colocamos seguidamente al EXECUTE:

GET DIAGNOSTICS rc = ROW_COUNT;

Si “rc” es igual a cero, entonces no existe el valor “vrecord” en


el campo “campo” de la tabla “tabla”, caso contrario, se retorna
NEW.

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;

Y listo!, definimos el cierre y que esta es una función VOLATILE:

END;
LANGUAGE plpgsql VOLATILE

Y ya podemos usarla.

Usando la función dinámica


Para usar la función dinámica, simplemente creamos un trigger
en cada tabla que necesite convocarla, pasando como
parámetros de la función trigger la tabla referencia y el campo
que debe evaluar, ejemplo:
CREATE TRIGGER trg_insert_detalle_reportes
BEFORE INSERT
ON reportes
FOR EACH ROW
EXECUTE PROCEDURE permitir_insert('reportes',
'id_reporte');

Se insertará un detalle de reporte, solamente si el valor de


“id_reporte” aparece en la tabla “reportes”.

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.

Obviamente esto se puede hacer desde el mismo programa de actualización, pero


resulta, y es este caso en particular lo que motivó el empleo de triggers, que son
varios programas los que actualizan esta tabla, así que mantener cada uno de
ellos se vuelve un tanto cuanto engorroso. La mejor solución es emplear triggers.
Cabe señalar que el ejemplo que se muestra a continuación funciona sólo de la
versión de PostgreSQL 6.5.3 en adelante, dado que emplea el Procedure
Language PL/pgSQL y algunas particularidades integradas a partir de esa
versión.

Bueno, el código para actualizar los valores es el siguiente:


DROP FUNCTION inc_aux ();
CREATE FUNCTION inc_aux () RETURNS OPAQUE AS '
DECLARE
myrec record;
BEGIN
SELECT * INTO myrec FROM aux WHERE aux.dmd5 = NEW.dmd5;
IF NOT FOUND THEN
INSERT INTO aux VALUES (NEW.dmd5, 1);
ELSE
UPDATE aux SET cont=cont+1 WHERE dmd5 = NEW.dmd5;
END IF;
RETURN NEW;
END;
' LANGUAGE 'plpgsql';
DROP TRIGGER ins_arc ON archivos;
CREATE TRIGGER ins_arc BEFORE INSERT ON archivos FOR EACH ROW
EXECUTE PROCEDURE inc_aux();

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.

El select se hace para averiguar si existe un registro con el MD5 y en caso de no


existir lo insertamos con el contador en uno y en caso de que ya exista,
incrementamos el contador en uno. Muy simple. Sólo es de notar que tuvimos
que declarar un registro para hacer la consulta y saber si el registro existe en la
tabla anexa para actualizarlo (update) o si será necesario crear uno nuevo
(insert). El registro NEW se refiere al registro con que el que fué disparado
el trigger.

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();

Una manera de probarlo, es la siguiente:


mancha=> SELECT * FROM aux WHERE dmd5='12345678901234567890123456789012';
dmd5|cont
----+----
(0 rows)
mancha=> INSERT INTO archivos VALUES ('BORRAME','/',12345,'31-12-
1999','23:59:07',
'12345678901234567890123456789012','12345678901234567890123456789012','Ca
serola');
INSERT 10981731 1
mancha=> SELECT * FROM aux WHERE dmd5='12345678901234567890123456789012';
dmd5|cont
--------------------------------+----
12345678901234567890123456789012| 1
(1 row)
mancha=> delete from archivos where arc='BORRAME';
DELETE 1
mancha=> SELECT * FROM aux WHERE dmd5='12345678901234567890123456789012';
dmd5|cont
----+----
(0 rows)

Ahora supongamos que tenemos un pequeño sistema de nómina y en el módulo


de ABC11.1 queremos tener la garantía mínima de que no se insertará un registro
sin nombre o con salario negativo. Además, queremos llevar nota de quién y
cuando modificó los registros. Sea la tabla:
CREATE TABLE emp (
nombre text,
salario int4,
last_date datetime,
last_user name);

Definimos la función que ``estampe'' los cambios:


CREATE FUNCTION emp_stamp () RETURNS OPAQUE AS '
BEGIN
-- Verifica que el nombre y el salario se hallan dado
IF NEW.nombre ISNULL THEN
RAISE EXCEPTION ''nombre no puede ser NULL'';
END IF;
IF NEW.salario ISNULL THEN
RAISE EXCEPTION ''% no puede tener un salario NULL'',
NEW.nombre;
END IF;
-- Que no tenga salario negativo (puede ser cero)
IF NEW.salario < 0 THEN
RAISE EXCEPTION ''% no puede tener un salario negativo'',
NEW.nombre;
END IF;
-- Ahora estampamos quién y cuándo hizo los cambios
NEW.last_date := ''now'';
NEW.last_user := getpgusername();
RETURN NEW;
END;
' LANGUAGE 'plpgsql';

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

Sin embargo, se nos olvidó hacer una verificación básica:


mancha=> insert into emp values ('', 1000000);
INSERT 11635173 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
|1000000|Wed 05 Jan 13:09:50 2000 CST|mancha
(5 rows)

Queda como ejercicio verificar que no se inserten nombres en blanco.

S-ar putea să vă placă și