PL/pgSQL peut �tre utilis� pour d�finir des proc�dures trigger. Une proc�dure trigger est cr��e gr�ce � la commande CREATE FUNCTION utilis�e comme fonction sans arguments ayant un type de retour trigger. Notez que la fonction doit �tre d�clar�e avec aucun argument m�me si elle s'attend � recevoir les arguments sp�cifi�s dans CREATE TRIGGER -- les arguments trigger sont pass�s via TG_ARGV, comme d�crit plus loin.
Quand une fonction PL/pgSQL est appel�e en tant que trigger, plusieurs variables sp�ciales sont cr��es automatiquement dans le bloc de plus haut niveau. Ce sont :
Type de donn�es RECORD ; variable contenant la nouvelle ligne de base de donn�es pour les op�rations INSERT/UPDATE dans les triggers de niveau ligne. Cette variable est NULL dans un trigger de niveau instruction et pour les op�rations DELETE.
Type de donn�es RECORD ; variable contenant l'ancienne ligne de base de donn�es pour les op�rations UPDATE/DELETE dans les triggers de niveau ligne. Cette variable est NULL dans les triggers de niveau instruction et pour les op�rations INSERT.
Type de donn�es name ; variable qui contient le nom du trigger r�ellement lanc�.
Type de donn�es text ; une cha�ne, soit BEFORE soit AFTER, soit INSTEAD OF selon la d�finition du trigger.
Type de donn�es text ; une cha�ne, soit ROW soit STATEMENT, selon la d�finition du trigger.
Type de donn�es text ; une cha�ne, INSERT, UPDATE, DELETE ou TRUNCATE indiquant pour quelle op�ration le trigger a �t� lanc�.
Type de donn�es oid ; l'ID de l'objet de la table qui a caus� le d�clenchement du trigger.
Type de donn�es name ; le nom de la table qui a caus� le d�clenchement. C'est obsol�te et pourrait dispara�tre dans une prochaine version. À la place, utilisez TG_TABLE_NAME.
Type de donn�es name ; le nom de la table qui a d�clench� le trigger.
Type de donn�es name ; le nom du sch�ma de la table qui a appel� le trigger.
Type de donn�es integer ; le nombre d'arguments donn�s � la proc�dure trigger dans l'instruction CREATE TRIGGER.
Type de donn�e text ; les arguments de l'instruction CREATE TRIGGER. L'index d�bute � 0. Les indices invalides (inf�rieurs � 0 ou sup�rieurs ou �gaux � tg_nargs) auront une valeur NULL.
Une fonction trigger doit renvoyer soit NULL soit une valeur record ayant exactement la structure de la table pour laquelle le trigger a �t� lanc�.
Les triggers de niveau ligne lanc�s BEFORE peuvent renvoyer NULL pour indiquer au gestionnaire de trigger de sauter le reste de l'op�ration pour cette ligne (les triggers suivants ne sont pas lanc�s, et les INSERT/UPDATE/DELETE ne se font pas pour cette ligne). Si une valeur non NULL est renvoy�e alors l'op�ration se d�roule avec cette valeur ligne. Renvoyer une valeur ligne diff�rente de la valeur originale de NEW modifie la ligne qui sera ins�r�e ou mise � jour. De ce fait, si la fonction de trigger veut que l'action r�ussise sans modifier la valeur de rang�e, NEW (ou une valeur �gale) doit �tre renvoy�e. Pour modifier la rang�e � �tre stock�e, il est possible de remplacer les valeurs directement dans NEW et renvoyer le NEW modifi� ou de g�n�rer un nouvel enregistrement � renvoyer. Dans le cas d'un before-trigger sur une commande DELETE, la valeur renvoy�e n'a aucun effet direct mais doit �tre non-nulle pour permettre � l'action trigger de continuer. Notez que NEW est nul dans le cadre des triggers DELETE et que renvoyer ceci n'est pas recommand� dans les cas courants. Une pratique utile dans des triggers DELETE serait de renvoyer OLD.
Les triggers INSTEAD OF (qui sont toujours des triggers au niveau ligne et peuvent seulement �tre utilis�s sur des vues) peuvent renvoyer NULL pour signaler qu'ils n'ont fait aucune modification et que le reste de l'op�ration pour cette ligne doit �tre ignor� (autrement dit, les triggers suivants ne sont pas d�clench�s et la ligne n'est pas compt�e dans le statut des lignes affect�es pour la requ�te INSERT/UPDATE/DELETE). Une valeur diff�rente de NULL doit �tre renvoy�e pour indiquer que le trigger a trait� l'op�ration demand�e. Pour les op�rations INSERT et UPDATE, la valeur de retour doit �tre NEW, que la fonction trigger peut modifier pour supporter une clause RETURNING d'une requ�te INSERT ou UPDATE (cela affectera aussi la valeur de la ligne pass�e aux autres triggers). Pour les requ�tes DELETE, la valeur de retour doit �tre OLD.
La valeur de retour d'un trigger de niveau rang�e d�clench� AFTER ou un trigger de niveau instruction d�clench� BEFORE ou AFTER est toujours ignor� ; il pourrait aussi bien �tre NULL. N�anmoins, tous les types de triggers peuvent toujours annuler l'op�ration compl�te en envoyant une erreur.
L'Exemple 39.3, � Une proc�dure trigger PL/pgSQL � montre un exemple d'une proc�dure trigger dans PL/pgSQL.
Exemple 39.3. Une proc�dure trigger PL/pgSQL
Cet exemple de trigger assure qu'� chaque moment o� une ligne est ins�r�e ou mise � jour dans la table, le nom de l'utilisateur courant et l'heure sont estampill�s dans la ligne. Et cela vous assure qu'un nom d'employ� est donn� et que le salaire est une valeur positive.
CREATE TABLE emp ( nom_employe text, salaire integer, date_dermodif timestamp, utilisateur_dermodif text ); CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$ BEGIN -- Verifie que nom_employe et salary sont donn�s IF NEW.nom_employe IS NULL THEN RAISE EXCEPTION 'nom_employe ne peut pas �tre NULL'; END IF; IF NEW.salaire IS NULL THEN RAISE EXCEPTION '% ne peut pas avoir un salaire', NEW.nom_employe; END IF; -- Qui travaille pour nous si la personne doit payer pour cela ? IF NEW.salaire < 0 THEN RAISE EXCEPTION '% ne peut pas avoir un salaire n�gatif', NEW.nom_employe; END IF; -- Rappelons-nous qui a chang� le salaire et quand NEW.date_dermodif := current_timestamp; NEW.utilisateur_dermodif := current_user; RETURN NEW; END; $emp_stamp$ LANGUAGE plpgsql; CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
Une autre fa�on de tracer les modifications sur une table implique la cr�ation d'une nouvelle table qui contient une ligne pour chaque insertion, mise � jour ou suppression qui survient. Cette approche peut �tre vue comme un audit des modifications sur une table. L'Exemple 39.4, � Une proc�dure d'audit par trigger en PL/pgSQL � montre un exemple d'une proc�dure d'audit par trigger en PL/pgSQL.
Exemple 39.4. Une proc�dure d'audit par trigger en PL/pgSQL
Cet exemple de trigger nous assure que toute insertion, modification ou suppression d'une ligne dans la table emp est enregistr�e dans la table emp_audit. L'heure et le nom de l'utilisateur sont conserv�es dans la ligne avec le type d'op�ration r�alis�.
CREATE TABLE emp ( nom_employe text NOT NULL, salaire integer ); CREATE TABLE emp_audit( operation char(1) NOT NULL, tampon timestamp NOT NULL, id_utilisateur text NOT NULL, nom_employe text NOT NULL, salaire integer ); CREATE OR REPLACE FUNCTION audit_employe() RETURNS TRIGGER AS $emp_audit$ BEGIN -- -- Ajoute une ligne dans emp_audit pour refl�ter l'op�ration r�alis�e -- sur emp, -- utilise la variable sp�ciale TG_OP pour cette op�ration. -- IF (TG_OP = 'DELETE') THEN INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*; RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*; RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*; RETURN NEW; END IF; RETURN NULL; -- le r�sultat est ignor� car il s'agit d'un trigger AFTER END; $emp_audit$ language plpgsql; CREATE TRIGGER emp_audit AFTER INSERT OR UPDATE OR DELETE ON emp FOR EACH ROW EXECUTE PROCEDURE audit_employe();
Une variation de l'exemple pr�c�dent utilise une vue joignant la table principale et la table d'audit pour montrer les derniers enregistrements modifi�s. Cette approche enregistre toujours toutes les modifications sur la table mais pr�sente aussi une vue simple de l'audit, n'affichant que le date et heure de la derni�re modification pour chaque enregistrement. Exemple 39.5, � Une fonction trigger en PL/pgSQL surune vue pour un audit � montre un exemple d'un trigger d'audit sur une vue avec PL/pgSQL.
Exemple 39.5. Une fonction trigger en PL/pgSQL surune vue pour un audit
Cet exemple utilise un trigger sur une vue pour la rendre modifiable, et s'assure que toute insertion, mise � jour ou suppression d'une ligne dans la vue est enregistr�e (pour l'audit) dans la table emp_audit. La date et l'heure courante ainsi que le nom de l'utilisateur sont enregistr�s, avec le type d'op�ration r�alis� pour que la vue affiche la date et l'heure de la derni�re modification de chaque ligne.
CREATE TABLE emp ( nom_employe text PRIMARY KEY, salaire integer ); CREATE TABLE emp_audit( operation char(1) NOT NULL, id_utilisateur text NOT NULL, nom_employe text NOT NULL, salaire integer, dmodif timestamp NOT NULL ); CREATE VIEW emp_vue AS SELECT e.nom_employe, e.salaire, max(ea.dmodif) AS derniere_modification FROM emp e LEFT JOIN emp_audit ea ON ea.nom_employe = e.nom_employe GROUP BY 1, 2; CREATE OR REPLACE FUNCTION miseajour_emp_vue() RETURNS TRIGGER AS $$ BEGIN -- -- Perform the required operation on emp, and create a row in emp_audit -- to reflect the change made to emp. -- IF (TG_OP = 'DELETE') THEN DELETE FROM emp WHERE nom_employe = OLD.nom_employe; IF NOT FOUND THEN RETURN NULL; END IF; OLD.derniere_modification = now(); INSERT INTO emp_audit VALUES('D', user, OLD.*); RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN UPDATE emp SET salary = NEW.salary WHERE nom_employe = OLD.nom_employe; IF NOT FOUND THEN RETURN NULL; END IF; NEW.derniere_modification = now(); INSERT INTO emp_audit VALUES('U', user, NEW.*); RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO emp VALUES(NEW.nom_employe, NEW.salaire); NEW.derniere_modification = now(); INSERT INTO emp_audit VALUES('I', user, NEW.*); RETURN NEW; END IF; END; $$ LANGUAGE plpgsql; CREATE TRIGGER emp_audit INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_vue FOR EACH ROW EXECUTE PROCEDURE miseajour_emp_vue();
Une utilisation des triggers est le maintien d'une table r�sum�e d'une autre table. Le r�sum� r�sultant peut �tre utilis� � la place de la table originale pour certaines requ�tes -- souvent avec des temps d'ex�cution bien r�duits. Cette technique est souvent utilis�e pour les statistiques de donn�es o� les tables de donn�es mesur�es ou observ�es (appel�es des tables de faits) peuvent �tre extr�mement grandes. L'Exemple 39.6, � Une proc�dure trigger PL/pgSQL pour maintenir une table r�sum�e � montre un exemple d'une proc�dure trigger en PL/pgSQL maintenant une table r�sum�e pour une table de faits dans un syst�me de donn�es (data warehouse).
Exemple 39.6. Une proc�dure trigger PL/pgSQL pour maintenir une table r�sum�e
Le sch�ma d�taill� ici est partiellement bas� sur l'exemple du Grocery Store provenant de The Data Warehouse Toolkit par Ralph Kimball.
-- -- Tables principales - dimension du temps de ventes. -- CREATE TABLE time_dimension ( time_key integer NOT NULL, day_of_week integer NOT NULL, day_of_month integer NOT NULL, month integer NOT NULL, quarter integer NOT NULL, year integer NOT NULL ); CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key); CREATE TABLE sales_fact ( time_key integer NOT NULL, product_key integer NOT NULL, store_key integer NOT NULL, amount_sold numeric(12,2) NOT NULL, units_sold integer NOT NULL, amount_cost numeric(12,2) NOT NULL ); CREATE INDEX sales_fact_time ON sales_fact(time_key); -- -- Table r�sum� - ventes sur le temps. -- CREATE TABLE sales_summary_bytime ( time_key integer NOT NULL, amount_sold numeric(15,2) NOT NULL, units_sold numeric(12) NOT NULL, amount_cost numeric(15,2) NOT NULL ); CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key); -- -- Fonction et trigger pour amender les colonnes r�sum�es -- pour un UPDATE, INSERT, DELETE. -- CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER AS $maint_sales_summary_bytime$ DECLARE delta_time_key integer; delta_amount_sold numeric(15,2); delta_units_sold numeric(12); delta_amount_cost numeric(15,2); BEGIN -- Travaille sur l'ajout/la suppression de montant(s). IF (TG_OP = 'DELETE') THEN delta_time_key = OLD.time_key; delta_amount_sold = -1 * OLD.amount_sold; delta_units_sold = -1 * OLD.units_sold; delta_amount_cost = -1 * OLD.amount_cost; ELSIF (TG_OP = 'UPDATE') THEN -- interdit les mises � jour qui modifient time_key - -- (probablement pas trop cher, car DELETE + INSERT est la fa�on la plus -- probable de r�aliser les modifications). IF ( OLD.time_key != NEW.time_key) THEN RAISE EXCEPTION 'Update of time_key : % -> % not allowed', OLD.time_key, NEW.time_key; END IF; delta_time_key = OLD.time_key; delta_amount_sold = NEW.amount_sold - OLD.amount_sold; delta_units_sold = NEW.units_sold - OLD.units_sold; delta_amount_cost = NEW.amount_cost - OLD.amount_cost; ELSIF (TG_OP = 'INSERT') THEN delta_time_key = NEW.time_key; delta_amount_sold = NEW.amount_sold; delta_units_sold = NEW.units_sold; delta_amount_cost = NEW.amount_cost; END IF; -- Insertion ou mise � jour de la ligne de r�sum� avec les nouvelles valeurs. <<insert_update>> LOOP UPDATE sales_summary_bytime SET amount_sold = amount_sold + delta_amount_sold, units_sold = units_sold + delta_units_sold, amount_cost = amount_cost + delta_amount_cost WHERE time_key = delta_time_key; EXIT insert_update WHEN found; BEGIN INSERT INTO sales_summary_bytime ( time_key, amount_sold, units_sold, amount_cost) VALUES ( delta_time_key, delta_amount_sold, delta_units_sold, delta_amount_cost ); EXIT insert_update; EXCEPTION WHEN UNIQUE_VIOLATION THEN -- do nothing END; END LOOP insert_update; RETURN NULL; END; $maint_sales_summary_bytime$ LANGUAGE plpgsql; CREATE TRIGGER maint_sales_summary_bytime AFTER INSERT OR UPDATE OR DELETE ON sales_fact FOR EACH ROW EXECUTE PROCEDURE maint_sales_summary_bytime(); INSERT INTO sales_fact VALUES(1,1,1,10,3,15); INSERT INTO sales_fact VALUES(1,2,1,20,5,35); INSERT INTO sales_fact VALUES(2,2,1,40,15,135); INSERT INTO sales_fact VALUES(2,3,1,10,1,13); SELECT * FROM sales_summary_bytime; DELETE FROM sales_fact WHERE product_key = 1; SELECT * FROM sales_summary_bytime; UPDATE sales_fact SET units_sold = units_sold * 2; SELECT * FROM sales_summary_bytime;