Sunteți pe pagina 1din 14

Capitolul 17.

Declanatoare
Declanatoarele (trigger-e, n original) reprezint o categorie deosebit de
proceduri stocate ale unei baze de date. Particularitatea lor esenial ine de faptul
c, odat create i stocate n schema bazei, acestea sunt executate automat la
operaiuni pentru care au fost definite. Tradiional, sunt trei tipuri de astfel de
operaiuni: inserare, modificare i tergere ale liniilor din tabela pentru care au fost
definite. La aceast tipologie a declanatoarelor, unele SGBD-uri au mai adugat i
alte categorii, asociate, spre exemplu, actualizrilor tabelelor virtuale, conectrii
unei aplicaii/utilizator la baza de date, deschiderii sau nchiderii bazei de date etc.
Pentru lucrarea de fa problema principal a declanatoarelor ine de faptul c
acestea sunt scrise n extensiile procedurale ale SQL care, ca i funciile i proce-
durile din capitolul anterior, prezint diferene sensibile de la produs la produs:
PL/SQL (Oracle), SQL PL (DB2), Transact-SQL (SQL Server), PL/pgSQL etc.
Cele mai importante avantaje ale declanatoarelor sunt:
permit instituirea unor reguli de validare cu mult mai complexe dect
posibilitile clauzei CHECK;
pot ameliora sensibil mecanismul de securitate al bazei;
permit instituirea (acolo unde nu este posibil prin clauza FOREIGN KEY)
restriciilor refereniale i modului de tratare a modificrilor ce pot cauza
probleme de integritate referenial;
constituie suportul calculrii automate a valorilor unor atribute, ceea ce
poate atrage un spor de vitez considerabil;
posibilitatea jurnalizrii explicite, n formatul dorit a tuturor operaiunilor
de tip DML i DML dintr-o (sub) schem a bazei de date;
sincronizarea replicilor unei baze de date distribuite/replicate.
17.1. Tipologia declanatoarelor
Aadar, aria de folosire a declanatoarelor este foarte extins. Totui, documen-
taiile de produs recomand ca elanul trigger-esc s nu depeasc anumite limite.
Prin declanatoare trebuie gestionate numai acele reguli imposibil de asigurat prin
restriciile la nivel de atribut (cmp), nregistrare, restricii de tip PRIMARY KEY,
NOT NULL etc. Tipologia clasic numr nu mai puin de 15 categorii de
triggere vezi tabelul 17.1.
Tabel 17.1. Tipologia clasic a declanatoarelor
Eveniment declanator Declanare pt.
fiecare
Descriere
BEFORE INSERT Comand Codul (programul) acestuia se lanseaz naintea
executrii unei comenzi INSERT n tabela int
772 Capitolul 17
BEFORE INSERT Linie Se execut naintea inserrii fiecrei linii n tabel
AFTER INSERT Linie Se execut dup inserarea fiecrei linii n tabel
AFTER INSERT Comand Se lanseaz dup execuia unei comenzi de inserare
de linii n tabel
INSTEAD OF INSERT Linie n loc s se insereze o linie n tabel, se execut
codul din acest declanator
BEFORE UPDATE Comand Codul acestuia se execut naintea executrii unei
comenzi UPDATE pentru tabela int
BEFORE UPDATE Linie Se execut naintea modificrii fiecrei linii din
tabel
AFTER UPDATE Linie Se execut dup modificarea fiecrei linii
AFTER UPDATE Comand Se lanseaz dup execuia comenzii UPDATE (dup
modificarea tuturor liniilor afectate de comand)
INSTEAD OF UPDATE Linie n loc s se modifice o linie din tabela int, se
execut codul din acest declanator
BEFORE DELETE Comand Se execut naintea comenzii DELETE
BEFORE DELETE Linie Se execut nainte de tergerea fiecrei linii
AFTER DELETE Linie Se execut dup tergerea fiecrei linii
AFTER DELETE Comand Se lanseaz dup execuia comenzii DELETE(dup
tergerea tuturor liniilor afectate de comand)
INSTEAD OF DELETE Linie Se execut n locul tergerii unei linii din tabela int

Declanatoare la nivel de linie i la nivel de comand (statement)
La definirea triggerului, se poate stabili de cte ori este executat acesta: de
fiecare dat cnd o linie este inserat/modificat/tears, sau o dat pentru o
comand de actualizare, indiferent de cte linii sunt afectate. S lum, spre
exemplu, comanda:
UPDATE clienti SET codcl = codcl + 100 WHERE codcl > 3

Presupunnd c 135 de clieni au codul mai mare dect 3, un trigger de
modificare la nivel de linie se execut de 135 de ori, n timp ce unul la nivel de
comand o singur dat. Dac nici un client nu are codul mai mare de 3, un trigger
de modificare la nivel de linie nu s-ar putea executa deloc, n timp ce declanatorul
declarat a fi la nivel de comand ar fi lansat (evident, o singur dat).

Declanatoare de tip nainte (BEFORE) i dup (AFTER) actualizare
Un trigger de tip BEFORE este ideal pentru verificarea unui set de condiii care
ar putea bloca de la bun nceput tranzacia respectiv (autentificri, soldul 0 pentru
un cont sau pentru un material ntr-o magazie etc.). Dac pentru tabel au fost
definite i restricii (NOT NULL, PRIMARY KEY, CHECK), dar i declanatoare de
tip BEFORE, declanatoarele BEFORE se lanseaz naintea verificrii restriciilor.
Declanatoarele de tip AFTER se execut dup verificarea restriciilor, fiind
nimerite la jurnalizare (ce ine evidena strict a modificrilor operate n tabele cu
informaii sensibile - spre exemplu, calculul automat al costului mediu ponderat al
unui material dup fiecare intrare n magazie sau calculul soldului curent al unui
cont bancar dup fiecare depunere sau retragere etc.). Spre deosebire de cele de tip
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 773
BEFORE , triggerele de tip AFTER blocheaz liniile procesate. Dac declanatoarele
prelucreaz/interogheaz tabelele pe care au fost denumite
1
, declanatoarele de tip
BEFORE nu vd modifificrile care atras declanarea lor, n timp ce triggerele
AFTER vd imaginea bazei de date dup modificare.
Orice declanator la nivel de linie sau comand poate fi de tip BEFORE sau
AFTER. Astfel, pentru orice tabel i operaie de inserare/modificare/tergere pot
definite patru tipuri de triggere care se execut n ordinea:
nainte - la nivel de comand (BEFORE - statement)
nainte - la nivel de linie (BEFORE - row)
dup - la nivel de linie (AFTER - row)
dup - la nivel de comand (AFTER - statement)

Triggere de tip n-loc-de (INSTEAD OF)
Acest gen de declanator constituie un excelent mijloc de propagare a modific-
rilor dintr-o tabel virtual n tabelele de baz din care provine. Amnm aceast
discuie pentru finalul fiecrui paragraf, ncepnd cu urmtorul.

Un alt element important este c, i n trigger-ele la nivel de linie (ROW) de tip
BEFORE i n cele de tip AFTER, pot fi invocate i prelucrate att valorile
atributelor dinaintea operaiei, ct i cele de dup operaie. Prefixul este, dup caz,
:OLD/OLD sau :NEW/NEW.
17.2. Declanatoare n PL/pgSQL
Limbajul PL/pgSQL este generos n materie de declanatoare. S ncepem cu
unul simplu dedicat cheilor surogat. Atributul CodCl din tabela CLIENTI este o
asemenea cheie surogat, ntruct valorile sale nu au nicio legtur cu valorile
celorlalte atribute ale fiecrui client, fiind mai mult un soi de numr curent. Primii
clieni au fost inserai nc din capitolul 3. Din acest moment, dorim ca, la inserare,
codul fiecrui client s nu mai depind de utilizator (valoarea specificat n
comanda INSERT), ci s fie atribuit automat de sistem. Pentru aceasta, vom crea
un obiect al bazei de date de care am pomenit n capitolul 2, i anume o secven:
CREATE SEQUENCE seq_codcl START WITH 1011 INCREMENT BY 1
MINVALUE 1001 MAXVALUE 8900 NO CYCLE ;
O secven este un obiect dintr-o baz de date care genereaz o valoare unic
atunci cnd i se cere. Chiar i atunci cnd cteva mii de utilizatori ar introduce
simultan clieni, vrem s fim siguri c valoarea fiecruia este unic. Or secvena




1
Nu ntotdeauna este permis interogarea unei tabele pentru care s-au definit declanatoare la
nivel de linie (ROW), mai ales n caz declanatoarelor de tip UPDATE. Cei dintre dvs. Au lucrat n
Oracle, au avut, probabil, de a face cu problema mutanei tabelei.
774 Capitolul 17
este construit astfel ca eventualele cereri de valori unice s fie serializate. Definim
acum un declanator de tip BEFORE-INSERT-ROW vezi listing 17.1. PL/pgSQL
reclam crearea unei funcii care s returneze TRIGGER, i apoi o comand
CREATE TRIGGER care s foloseasc funcia respectiv drept argument.
Listing 17.1. Funcia-declanator PL/pgSQL executat la inserarea unei nregistrri n
tabela CLIENTI
CREATE OR REPLACE FUNCTION trg_clienti_ins_before_row()
RETURNS TRIGGER AS $trg_clienti_ins_before_row$
BEGIN
NEW.CodCl = NEXTVAL('seq_codcl') ;
RETURN NEW ;
END ;
$trg_clienti_ins_before_row$ LANGUAGE plpgsql ;


Extragerea urmtoarei valori unice disponibile din secven se realizeaz prin
funcia NEXTVAL. NEW.CodCl semnalizeaz c valoarea generat de secven va fi
atribuit atributului CodCl de pe linia nou-introdus. Funcia se termin obligatoriu
prin RETURN NEW, ceeea ce nseamn c se returneaz noua linie inserat n care
au fost fcute eventuale modificri. Asociem funcia declanatorului printr-o
comand CREATE TRIGGER:
CREATE TRIGGER trg_clienti_ins_before_row
BEFORE INSERT ON clienti
FOR EACH ROW EXECUTE PROCEDURE trg_clienti_ins_before_row()
Reinem c modificarea valorii unuia sau multor atribute de pe linia proaspt
inserat este posibil doar n declanatoare BEFORE-ROW. Acum s testm funci-
onalitatea declanatorului. Introducem un client:
INSERT INTO clienti VALUES (999, 'Client destul de nou', 'R12345',
'Adresa clientului nou', 700505, NULL)
Valoarea CodCl specificat n clauza VALUES este 999. Dac ns vizualizm
noua nregistrare (ultima din figura 17.1) observm c PostgreSQL a inut cont de
declanator, i nu de ceea ce am specificat noi n comanda INSERT.
Continum cu declanatorul de inserare n tabela FACTURI. Pentru a fi siguri
c orice factur nou introdus pornete de la zero cu valorile total, ncasat i tva,
declarm un declanator dup cum urmeaz (listing 17.2).
Listing 17.2. Declanatorul PL/pgSQL pentru inserri n tabela FACTURI
CREATE OR REPLACE FUNCTION trg_facturi_ins()
RETURNS TRIGGER AS $trg_facturi_ins$
BEGIN
NEW.valtotala := 0 ;
NEW.tva := 0 ;
NEW.valincasata := 0 ;

RETURN NEW ;
END ;
$trg_facturi_ins$ LANGUAGE plpgsql ;

SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 775

Figura 17.1. Verificarea declanatorului de inserare n CLIENTI
CREATE TRIGGER trg_facturi_ins BEFORE INSERT ON facturi
FOR EACH ROW EXECUTE PROCEDURE trg_facturi_ins() ;

Atributele ValTotala i TVA ale tabelei FACTURI trebuie recalculate dup
fiecare inserare, (eventual) modificare i tergere a unei linii din factura respectiv,
altfel spus la INSERT-uri, DELETE-uri i unele UPDATE-uri asupra tabelei
LINIFACT. Aa nct, ca model, vom redacta declanatorul de inserare n tabela
LINIIFACT vezi listing 17.3 -, care modific valorile atributelor:
Linie si TVALinie din LINIIFACT,
TVA si VALTotala din FACTURI
Listing 17.3. Declanatorul PL/pgSQL de inserare n tabela LINIIFACT
DROP FUNCTION trg_liniifact_ins() CASCADE ;

CREATE OR REPLACE FUNCTION trg_liniifact_ins()
RETURNS TRIGGER AS $trg_liniifact_ins$
BEGIN
NEW.tvalinie := COALESCE(NEW.cantitate,0) * COALESCE(NEW.pretunit,0) *
f_proctva(NEW.codpr) ;
NEW.linie := (SELECT COALESCE(MAX(Linie),0) + 1 FROM liniifact
WHERE NrFact=NEW.NrFact) ;

UPDATE facturi SET
tva = COALESCE(tva,0) + NEW.tvalinie,
valtotala = COALESCE(valtotala,0) + NEW.cantitate * NEW.pretunit + NEW.tvalinie
WHERE nrfact = NEW.nrfact ;
RETURN NEW ;
END ;
776 Capitolul 17
$trg_liniifact_ins$ LANGUAGE plpgsql ;


CREATE TRIGGER trg_liniifact_ins BEFORE INSERT ON liniifact
FOR EACH ROW EXECUTE PROCEDURE trg_liniifact_ins() ;

Haidei s testm cele dou declanatoare, adugnd factura 3500 care conine
dou linii n care apar produsele ce au codurile 2 i 3:
INSERT INTO facturi VALUES (3500, CURRENT_DATE, 1002,
null, 33333333,null) ;
INSERT INTO liniifact VALUES (3500, 1, 2, 200, 100, 8989989) ;
INSERT INTO liniifact VALUES (3500, 2, 3, 100, 100, 0) ;

Figura 17.2 arat coninutul nregistrrilor din tabelele FACTURI i LINIIFACT
care se refer la factura 3500, valorile calculate fiind, cel puin n aparen, corecte.

Figura 17.2. Verificarea declanatoarelor de inserare n tabelele FACTURI i LINIIFACT
Dac la inserarea unei linii dintr-o factur (tabela LINIIFACT), valorile facturate
i ale TVA (din FACTURI) trebuie incrementate, la tergerea unei linii, valorile
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 777
celor dou atribute trebuie sczute. Poate aprea, ns, i o problem de numero-
tare a liniilor. Astfel, dac factura are cinci linii, iar linia tears este a doua, n
urma tergerii dup linia 1 urmeaz 3. Acest gol ar putea fi suprtor pentru unii,
aa c avem dou variante. Prima este cea restrictiv: nu se permite dect tergerea
ultimei linii dintr-o factur vezi listing 17.4.
Listing 17.4. Declanatorul PL/pgSQL de tergere n tabela LINIIFACT versiunea 1.0
CREATE OR REPLACE FUNCTION trg_liniifact_del()
RETURNS TRIGGER AS $trg_liniifact_del$
DECLARE
v_nrlinii NUMERIC(3) ;
BEGIN
SELECT MAX(linie) INTO v_nrlinii FROM liniifact WHERE nrfact = OLD.nrfact ;
IF OLD.linie <> COALESCE(v_nrlinii,0) THEN
RAISE EXCEPTION 'Nu puteti sterge decit ultima linie dintr-o factura ' ;
END IF ;

UPDATE facturi
SET tva = tva - OLD.tvalinie,
valtotala = valtotala - OLD.cantitate * OLD.pretunit
- OLD.tvalinie
WHERE nrfact = OLD.nrfact ;
RETURN OLD ;
END ;
$trg_liniifact_del$ LANGUAGE plpgsql ;

CREATE TRIGGER trg_liniifact_del BEFORE DELETE ON liniifact
FOR EACH ROW EXECUTE PROCEDURE trg_liniifact_del() ;
Dac dup crearea acestui declanator, ncercm s tergem prima linie a factu-
rii 3500:
DELETE FROM liniifact WHERE NrFact=3500 AND linie=1
obinem mesajul de eroare din figura 17.3.

Figura 17.3. Verificarea declanatoului de tergere n tabela LINIIFACT (v1.0)
778 Capitolul 17
Ne simim vinovai pentru rigiditatea soluiei, aa c ne vom gndi la o alta, n
care utilizatorul poate terge orice linie dintr-o factur, iar liniile vor fi renumero-
tate automat. Iat cum ar putea arta aceast versiune (listing 17.5).
Listing 17.5. Declanatorul PL/pgSQL de tergere n tabela LINIIFACT versiunea 2.0
-- varianta 2 - se permite stergerea oricarei linii si se renumeroteaza liniile acelei facturi

DROP FUNCTION trg_liniifact_del() CASCADE ;

CREATE OR REPLACE FUNCTION trg_liniifact_del()
RETURNS TRIGGER AS $trg_liniifact_del$
DECLARE
v_liniemax liniifact.linie%TYPE ;
BEGIN
SELECT MAX(linie) INTO v_liniemax FROM liniifact
WHERE nrfact = OLD.NrFact ;

IF v_liniemax = OLD.linie THEN
NULL ;
ELSE
UPDATE liniifact SET linie = linie - 1 WHERE linie > OLD.linie
AND NrFact= OLD.NrFact ;
END IF ;

UPDATE facturi
SET tva = tva - OLD.tvalinie,
valtotala = valtotala - OLD.cantitate * OLD.pretunit - OLD.tvalinie
WHERE nrfact = OLD.nrfact ;

RETURN OLD ;
END ;
$trg_liniifact_del$ LANGUAGE plpgsql ;

CREATE TRIGGER trg_liniifact_del AFTER DELETE ON liniifact
FOR EACH ROW
EXECUTE PROCEDURE trg_liniifact_del() ;
Testm validitatea declanatorului tergnd prima linie din facturra 3500
2
, iar
dup cum o arat figura 17.4, ex - a doua linie a devenit prima.:
DELETE FROM liniifact WHERE NrFact=3500 AND linie=1

Pn acum am rezolvat doar o parte a problemei atributelor calculate, cea mai
spumoas, i anume calcularea lor efectiv. Ne-a rmas ceva de pus la punct, fr
de care corectitudinea atributelor calculate are fi ndoielnic. S imaginm o
situaie n care s-ar cuveni s restricionm operaiunile de actualizare a tabelei
FACTURI. Modificrile valorilor pentru atributele ValTotala i TVA trebuie s fie
permise numai dac sunt consecin a declanatoarelor tabelei LINIIFACT. Dac




2
Cele dou linii ale facturii 3550 dinainte acestei tergeri sunt vizibile n figura 17.2.
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 779
nu am face o asemenea verificare, orice utilizator ar putea s modifice direct
(printr-o comand UPDATE) valoarea total a unei facturi i, astfel, s-ar crea o
diferen fa de informaiile din tabela LINIIFACT.

Figura 17.4. Verificarea declanatorului de tergere n tabela LINIIFACT (v2.0)
Ideea este una simpl: atunci cnd se modific valoarea atributelor ValTotala i
TVA, declanatorul de modificare (UPDATE) din FACTURI verific dac
modificarea provine din unul dintre declanatoarele (INSERT, UPDATE sau
DELETE) tabelei LINIIFACT. Dac sursa modificrii este un declanator din
LINIIFACT, modificarea este autorizat; n caz contrar, este interzis.
n PostgreSQL nu exist variabile publice, aa c vom folosi o tabel pe care o
vom numi, ostentativ, VAR_PUB:
CREATE TABLE var_pub (
utilizator VARCHAR(40) NOT NULL,
v_trg_liniifact BOOLEAN NOT NULL DEFAULT FALSE,
v_trg_incasfact BOOLEAN NOT NULL DEFAULT FALSE
) ;
Tabela va avea cte o linie pentru fiecare utilizator conectat la baza de date.
Celelalte dou atribute, ambele de tip BOOLEAN
3
, vor avea valoarea logic TRUE
dac este n curs de execuie unul dintre declanatoarele tabelei LINIIFACT,
respectiv INCASFACT. V reamintesc (am discutat pe acest subiect i prin
capitolul 6) c putem afla numele utilizatorului curent conectat la baza de date prin
funcia sistem USER sau CURRENT_USER:
SELECT USER
ntruct nu putem anticipa care sunt utilizatorii care vor lucra cu baza de date,
putem gndi o procedur (tii deja c n PL/pgSQL procedurile sunt tot funcii)




3
mi face plcere s v reamintesc c, dintre cele patru servere BD, doar PostgreSQL accept n
tabele atribute de tip BOOLEAN.
780 Capitolul 17
care s creeze, la apelare, o nregistrare n tabela VAR_PUB, dac aceast
nregistrare nu exist dinainte. Iat funcia-procedur n listing 17.6.
Listing 17.6. Funcia PL/pgSQL care inserareaz n VAR_PUB, dac e nevoie, o
nregistrare pentru utilizatorul curent
CREATE OR REPLACE FUNCTION p_init_vp () RETURNS VOID AS $$
BEGIN
IF NOT EXISTS (SELECT * FROM var_pub WHERE utilizator = USER) THEN
INSERT INTO var_pub VALUES (USER, FALSE, FALSE) ;
END IF ;
END ;
$$LANGUAGE plpgsql ;

Pentru o apela direct, folosim:
SELECT p_init_vp()
Putem verifica dac funcia funcioneaz prin:
SELECT * FROM var_pub

n continuare vezi listing 17.7 crem dou funcii ce furnizeaz valorile cu-
rente (din tabela VAR_PUB) ale atributelor v_trg_liniifact i v_trg_incasfact pentru
utilizatorul curent.
Listing 17.7. Funcii PL/pgSQL ce extrag valorile atributelor v_trg_liniifact i v_trg_incasfact
pentru utilizatorul curent
CREATE OR REPLACE FUNCTION f_trg_liniifact () RETURNS BOOLEAN AS $$
DECLARE
v BOOLEAN := FALSE ;
BEGIN
SELECT v_trg_liniifact INTO v FROM var_pub WHERE utilizator = USER ;
RETURN v ;
END ;
$$ LANGUAGE plpgsql ;


CREATE OR REPLACE FUNCTION f_trg_incasfact () RETURNS BOOLEAN AS $$
DECLARE
v BOOLEAN := FALSE ;
BEGIN
SELECT v_trg_incasfact INTO v FROM var_pub WHERE utilizator = USER ;
RETURN v ;
END ;
$$ LANGUAGE plpgsql ;


Tot pentru a ne uura munca vom crea o funcie-procedur care va seta
valoarea unuia dintre cele dou cmpuri pe TRUE sau FALSE vezi listing 17.8.
Listing 17.8. Funcie-procedur de setare a valorilor atributelor v_trg_liniifact i
v_trg_incasfact pentru utilizatorul curent
CREATE OR REPLACE FUNCTION p_set (atribut_ VARCHAR, valoare_ BOOLEAN)
RETURNS VOID AS $$
BEGIN
IF UPPER(atribut_) = 'V_TRG_LINIIFACT' THEN
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 781
UPDATE var_pub SET v_trg_liniifact = valoare_ WHERE utilizator = USER ;
ELSE
IF UPPER(atribut_) = 'V_TRG_INCASFACT' THEN
UPDATE var_pub SET v_trg_incasfact = valoare_ WHERE utilizator = USER ;
END IF ;
END IF ;
END ;
$$ LANGUAGE plpgsql ;


Toate aceste funcii constituie accesorii pentru seria declanatoarelor care
urmeaz. ncepem cu declanatorul de inserare n LINIIFACT listing 17.9.
Pstrm varianta anterioar, la care adugam un apel (prin comanda PERFORM) la
procedura p_init_vp, i setarea pe TRUE a valorii atributului v_trg_liniifact pentru
utilizatorul curent. Dup incrementarea valorilor atributelor ValTotala i TVA,
readucem valoarea atributului v_trg_liniifact pentru utilizatorul curent la starea
FALSE.
Listing 17.9. O nou versiune a declanatorului de inserare a unei nregistrri n LINIIFACT
DROP FUNCTION trg_liniifact_ins() CASCADE ;
CREATE OR REPLACE FUNCTION trg_liniifact_ins() RETURNS TRIGGER AS $trg_liniifact_ins$
BEGIN
NEW.tvalinie := NEW.cantitate * NEW.pretunit * f_proctva(NEW.codpr) ;

-- noutate !!!
PERFORM p_init_vp() ;
PERFORM p_set ('v_trg_liniifact', TRUE) ;
UPDATE facturi SET tva = tva + NEW.tvalinie,
valtotala = valtotala + NEW.cantitate * NEW.pretunit + NEW.tvalinie
WHERE nrfact = NEW.nrfact ;

-- refacem valoarea pseudo-variabilei
PERFORM p_set ('v_trg_liniifact', FALSE) ;
RETURN NEW ;
END ;
$trg_liniifact_ins$ LANGUAGE plpgsql ;

CREATE TRIGGER trg_liniifact_ins BEFORE INSERT ON liniifact
FOR EACH ROW EXECUTE PROCEDURE trg_liniifact_ins() ;

Declanatorul de modificare a unei linii n LINIIFACT este ceva mai interesant,
pentru c e prima versiune discutat (n listing 17.10).
Listing 17.10. Noua (de fapt, e prima!) versiune a declanatorului de modificare a unei
nregistrri din LINIIFACT
DROP FUNCTION trg_liniifact_upd_br() CASCADE ;

CREATE OR REPLACE FUNCTION trg_liniifact_upd_br()
RETURNS TRIGGER AS $trg_liniifact_upd_br$
BEGIN
IF NEW.nrfact = OLD.nrfact THEN -- nu s-a schimbat nrfact !!!!
IF NEW.codpr <> OLD.codpr OR NEW.cantitate <> OLD.cantitate OR
NEW.pretunit <> OLD.pretunit OR NEW.tvalinie <> OLD.tvalinie THEN
782 Capitolul 17
NEW.tvalinie := NEW.cantitate * NEW.pretunit * f_proctva(NEW.codpr) ;

PERFORM p_init_vp() ;
PERFORM p_set ('v_trg_liniifact', TRUE) ;

UPDATE facturi SET tva = tva - OLD.tvalinie + NEW.tvalinie,
valtotala = valtotala - (OLD.cantitate * OLD.pretunit + OLD.tvalinie)
+ (NEW.cantitate * NEW.pretunit + NEW.tvalinie)
WHERE nrfact = NEW.nrfact ;

PERFORM p_set ('v_trg_liniifact', FALSE) ;

END IF ;
ELSE -- vechile valori trebuie scazute de la "vechea" factura
-- iar noile valori adaugate "noii" facturi
PERFORM p_init_vp() ;
PERFORM p_set ('v_trg_liniifact', TRUE) ;
UPDATE facturi SET tva = tva - OLD.tvalinie,
valtotala = valtotala - (OLD.cantitate * OLD.pretunit + OLD.tvalinie)
WHERE nrfact = OLD.nrfact ;

NEW.tvalinie := NEW.cantitate * NEW.pretunit * f_proctva(NEW.codpr) ;
UPDATE facturi SET tva = tva + NEW.tvalinie,
valtotala = valtotala + (NEW.cantitate * NEW.pretunit + NEW.tvalinie)
WHERE nrfact = NEW.nrfact ;

-- refacem valoarea pseudo-variabilei
PERFORM p_set ('v_trg_liniifact', FALSE) ;
END IF ;
RETURN NEW ;
END ;
$trg_liniifact_upd_br$ LANGUAGE plpgsql ;

CREATE TRIGGER trg_liniifact_upd_br BEFORE UPDATE ON liniifact
FOR EACH ROW EXECUTE PROCEDURE trg_liniifact_upd_br() ;
Listing-ul 17.11 conine ultimul dintre cele trei declanatoare ale tabelei LINII-
FACT cel dedicat tergerii unei linii.
Listing 17.11. Noua versiune a declanatorului de tergere a unei nregistrri din LINIIFACT
DROP FUNCTION trg_liniifact_del() CASCADE ;

DROP FUNCTION trg_liniifact_del2() CASCADE ;
CREATE OR REPLACE FUNCTION trg_liniifact_del2()
RETURNS TRIGGER AS $trg_liniifact_del2$
BEGIN
-- noutate !!!
PERFORM p_init_vp() ;
PERFORM p_set ('v_trg_liniifact', TRUE) ;
--
UPDATE facturi
SET tva = tva - OLD.tvalinie,
valtotala = valtotala - OLD.cantitate * OLD.pretunit - OLD.tvalinie
WHERE nrfact = OLD.nrfact ;

PERFORM p_set ('v_trg_liniifact', FALSE) ;
RETURN OLD ;
END ;
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 783
$trg_liniifact_del2$ LANGUAGE plpgsql ;

CREATE TRIGGER trg_liniifact_del2 BEFORE DELETE ON liniifact
FOR EACH ROW
EXECUTE PROCEDURE trg_liniifact_del2() ;
Cele trei declanatoare asigur semnalizarea util declanatorului de modificare
n FACTURII, declanator care trebuie s discearn o modificare bun de una rea
vezi listing 17.12.
Listing 17.12. Declanatorul de modificare a unei nregistrri din FACTURI
DROP FUNCTION trg_facturi_upd() CASCADE ;

CREATE OR REPLACE FUNCTION trg_facturi_upd()
RETURNS TRIGGER AS $trg_facturi_upd$
BEGIN
IF NEW.tva <> OLD.tva OR NEW.valtotala <> OLD.valtotala THEN
PERFORM p_init_vp() ;
IF NOT f_trg_liniifact ()THEN
RAISE EXCEPTION
'Nu puteti modifica interactiv nici valoarea facturii, nici TVA-ul acesteia !' ;
END IF ;
END IF ;
RETURN NEW ;
END ;
$trg_facturi_upd$ LANGUAGE plpgsql ;

CREATE TRIGGER trg_facturi AFTER UPDATE ON facturi FOR EACH ROW
EXECUTE PROCEDURE trg_facturi_upd() ;
Dup atta strduin, a sosit momentul verificrilor. S ncepem printr-o
tentativ frauduloas de a modifica direct valoarea atributului ValTotal:
UPDATE facturi SET ValTotala = 12345678 WHERE NrFact = 1111
Spre meritul lor (i al nostru) declanatoarele i fac datoria, dup cum
sugereaz figura 17.5.

Figura 17.5. Blocarea unei modificri imorale n tabela FACTURI
784 Capitolul 17
n schimb, dac tergem liniile rmase n factura 1111 i le re-adugm dup
scenariul din capitolul trei, lucrurile decurg fr incidente:
DELETE FROM liniifact WHERE NrFact = 1111 ;
INSERT INTO liniifact (nrfact, linie, codpr, cantitate, pretunit)
VALUES (1111, 1, 1, 50, 1000) ;
INSERT INTO liniifact (nrfact, linie, codpr, cantitate, pretunit)
VALUES (1111, 2, 2, 75, 1050) ;
INSERT INTO liniifact (nrfact, linie, codpr, cantitate, pretunit)
VALUES (1111, 3, 5, 500, 7060) ;

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