Sunteți pe pagina 1din 10

DECLANSATORI SQL in DB2

Introducere
Declanșatorii sunt obiecte care sunt asociate cu tabelele sau view-uri și definesc
acțiunile care au loc în mod automat la apariția unui eveniment INSERT, UPDATE
sau DELETE. Iată câteva exemple de cazuri în care s-ar putea dori să utilizeze
declansatori:
● Atunci când se introduc date , pentru a valida datele înainte ca operația de
inserție să aibă loc.
● la actualizarea, pentru a compara noua valoare cu valoarea existentă pentru a
o valida pentru corectitudine. Acest lucru este util mai ales în cazul în care
tabelul utilizează o coloană care păstrează informații de stare și doriți să
definiți tranziții intre stări valide.
● La ștergere, pentru a insera automat informațiile într-un log pentru a se folosi
în scopuri de audit.
.
Declanșatori pot fi utilizati pentru centralizarea aplicării regulilor de business la
nivelul bazei de date, eliberând toate aplicațiile și utilizatorii de sarcina de a verifica
ei înșiși valabilitatea datelor. De asemenea, în cazul în care există vreodată o
schimbare în regulile de afaceri, aceste modificări sunt centralizate la nivelul bazei
de date,fără a a mai fi nevoie să se propage prin toate aplicațiile de executare.

Exemplu

Pentru a ilustra, exemplul următor creează tabele și declanșatori pentru baza de


date a unei companii fictive, care păstrează datele pentru comenzi și informații
despre clienți. Următoarele reguli de afaceri trebuie puse în aplicare:
1. Atunci când o comandă este primită de către companie, suma valorii comenzii
și valoarea tuturor facturilor neachitate pentru client nu poate depăși linia de
credit furnizată acelui client.
2. Un ordin de comandă poate avea mai multe stări: AȘTEPTARE (​PENDING ​),
ANULAT(​CANCELLED​), LIVRAT(​DELIVERED​) , EXPEDIAT(​SHIPPED) și
FINALIZAT(​COMPLETED​). Numai următoarele tranziții de stare sunt valabile:
AȘTEPTARE -> EXPEDIAT -> LIVRAT -> FINALIZAT
AȘTEPTARE -> ANULAT
3. Ștergerea unei comenzi este permisă numai în cazul în care acesta este în
starea ANULAT.
4. De asemenea, comenzile șterse vor fi scrise într-un alt tabel în scopuri de
audit.
Scriptul care crează tabele este prezentat mai jos.

create table customer_t (


cust_id INT NOT NULL PRIMARY KEY,
company_name VARCHAR(100),
credit DECIMAL(10,2))
create table product_t (
product_id INT NOT NULL PRIMARY KEY,
product_name VARCHAR(100))
create table orders_t (
order_id INT NOT NULL PRIMARY KEY,
cust_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
status CHAR(9) NOT NULL,
FOREIGN KEY (cust_id) REFERENCES
customer_t,
FOREIGN KEY (product_id) REFERENCES
product_t)
create table delete_log_t ( Text varchar(1000))

Vom defini următoarele secvențe pentru a genera ID-uri unice:

create sequence cust_seq


create sequence prod_seq
create sequence ord_seq

.
În continuare, vom insera următoarele date:
insert into customer_t values
(NEXTVAL FOR cust_seq, 'Nancys Widgets', 100)
insert into product_t values
(NEXTVAL FOR prod_seq, 'Blue Widgets')

Crearea declanșatorilor

În cele din urmă, vom crea declansatorii pentru a pune în aplicare logica de afaceri
definită mai sus,.

Primul declansator va implementa regula nr.1:


"Atunci când o comandă este primită de către companie, suma valorii comenzii și
valoarea tuturor facturilor neachitate pentru client nu poate depăși linia de credit
oferita pentru acel client."

1 : CREATE TRIGGER verify_credit


2 : NO CASCADE BEFORE INSERT ON orders_t
3 : REFERENCING NEW AS n
4 : FOR EACH ROW MODE DB2SQL
5 : BEGIN ATOMIC
6: DECLARE current_due DECIMAL(10,2) DEFAULT 0;
7: DECLARE credit_line DECIMAL(10,2);
9: /* * Se obtine valoarea linei de credit al clientului */
10: SET credit_line = (SELECT credit
FROM customer_t c
WHERE c.cust_id=n.cust_id);
11: -- Se sumarizeaza valoarea creditului
12: FOR ord_cursor AS
13: SELECT quantity, price
FROM orders_t ord
WHERE ord.cust_id=n.cust_id AND
status not IN ('COMPLETED','CANCELLED') DO
14: SET current_due = current_due +
(ord_cursor.price * ord_cursor.quantity);
15: END FOR;
16: IF (current_due + n.price * n.quantity) > credit_line THEN
17: SIGNAL SQLSTATE '80000' ('Order Exceeds credit line');
18: END IF;
19: END

Observatii:

● Declaratia CREATE TRIGGER de la linia 1 spune pur și simplu, vom crea un


obiect de tip trigger cu numele verify_credit.
● NO CASCADE BEFORE INSERT ON orders_t ​înseamnă că declansatorul va
acționa inainte de inserarea datelor in tabel iar actiunile declansatorului curent
nu vor provoca alte modificări in cascadă.. Declarația NO CASCADE este
obligatorie la orice declanșator de tipul BEFORE..
● REFERENCING NEW AS n i ​specifică faptul că randul care se vrea a fi
inserat poartă numele n.
● FOR EACH ROW înseamnă că acest declanșator va fi activat o singură dată
pentru fiecare rând care este inserat. Cealaltă opțiune este ​FOR EACH
STATEMENT (se aplică doar delansatoarelor de tip AFTER​),specifică faptul
că declanșatorul este activat o singură dată pentru o comandă SQL. Cu alte
cuvinte, în cazul în care o instrucțiune INSERT inserează valori selectând 10
rânduri dintr-un alt tabel, declanșatorul se va activa de 10 ori.. Dacă este
specificat ​FOR EACH STATEMENT​, declanșatorul este executat doar o
singură dată.
● MODE DB2SQL este o altă clauză care trebuie specificată.
● BEGIN ATOMIC de pe linia 5 și END pe linia 19 definesc corpul
declanșatorului. Opțiunea ATOMIC specifică faptul că acțiunile definite în
cadrul declanșatorului sunt executate toate sau nici una. . În cazul în care
orice erori apar în timpul execuției declanșatorului , toate acțiunile sunt rulate
înapoi pentru a menține integritatea datelor.
● DECLARE <variableName> <type> [DEFAULT <value>] ​pe liniile 6 și 7
definesc variabile locale pe care declanșatorul va trebui să le utilizeze pentru
a procesa regulile de business.
● Liniile 9 și 11 ilustrează cele două forme de comentarii, care sunt acceptate în
DB2 SQL PL. . Aveți posibilitatea să utilizați / * și * / pentru a face multi-linie
comentând, și utilizarea "- -" pentru a face o singură linie de comentariu.
● În linia 10, s-a efectuat SELECT pentru a afla valoarea liniei de credit al
clientului i. Predicatul pentru această Porțiunea de "n.cust_id" din clauza
WHERE se referă la valoarea din coloana furnizată de către instrucțiunea
INSERT care a activat acest declanșator.
● În continuare, în linia 12, vom defini o buclă care selectează toate
înregistrările plăților restante din tabela ORDERS și definește un cursor numit
ord_cursor read-only. Cursorul selectează prețul și cantitatea fiecărei comenzi
a cărui status nu este REALIZAT (plată primită) și nici CANCELLED (ordinul
nu a fost expediat). Cu fiecare rând a datelor rezultate, vom rezuma valoarea
comenzilor neplătite pentru a calcula totalul plăților restante în prezent.
● În cele din urmă, în linia 16, trigger-ul compară soldul datorat și valoarea noii
comenzi cu linia de credit la dispoziția clientului. În cazul în care totalul
depaseste linia de credit disponibilă, o eroare de aplicație este aruncata
utiliz[nd un code de eroare personalizat SQLSTATE 80,000 (folosind
instrucțiunea SIGNAL) și un mesaj de eroare "Comanda depășește linia de
credit" Insertul este respins și orice modificări sunt rulate înapoi. Eroarea
determina o eroare de tip SQLException care poate fi manipulata de către
aplicația apelatoare.
Notă: Limitarea actuală privind lungimea mesajului de eroare este de 70 de
caractere. În cazul în care mesajul depășește această limită, mesajul va fi trunchiat
fără avertisment.

In exemplul de mai sus, noi presupunem că o comandă INSERT inserează o singură


inregistrare, . În cazul în care cererea ar insera mai multe rânduri într-o singură
instrucțiune INSERT,declansatorul nostru ar trebui modificat si transformat intr-un
declansator de tip AFTER, deoarece succesiunea evenimentelor care au loc este
urmatoarea:
1. Utilizatorul sau aplicație emite o instrucțiune INSERT
2. Înainte ca inserarea sa fie completata se declansaza declanșatorul și se
execută pana la finalizare
3. In cazul în care declanșatorul se termină fără eroare, se introduce rândul.
Deoarece declanșatorii BEFORE se executa înainte de a se introduce datele, în
situațiile în care o singură instrucțiune INSERT inserează mai mult de un
rând,declansatorul nu va vedea toate rândurile pe care utilizatorul sau aplicația
încearcă a le insera.
Există unele optimizări care se pot face pentru a mării viteza de executie a
declansatorului. Trigger-ul a fost scrist în principal pentru a ilustra modul de utilizare
a unor noi funcționalități și nu pentru performanță. A se vedea, sfaturi pentru
performanță pentru mai multe informații.
Declansatori de tip UPDATE

Sunt foarte similari cu declansatorii INSERT cu excepția faptului că atât noua


valoarea cat si cea existent[ pot fi accesate. De la regulile noastre de afaceri de mai
sus, dorim să folosim declanșatori pentru a defini tranziții de stare valide care să se
aplice în toate aplicațiile.
Tranziții de stare valide:

PENDING -> SHIPPED -> DELIVERED -> COMPLETED


PENDING -> CANCELLED

Următorul declanșator poate fi folosit pentru a pune în aplicare aceste tranziții:

1 : CREATE TRIGGER verify_state


2 : NO CASCADE BEFORE UPDATE ON orders_t
3 : REFERENCING OLD AS o NEW AS n
4 : FOR EACH ROW MODE DB2SQL
5 : BEGIN ATOMIC
6: IF o.status='PENDING' and n.status IN ('SHIPPED','CANCELLED')
THEN
7: -- valid state
8: ELSEIF o.status='SHIPPED' and
9: n.status ='DELIVERED' THEN
10: -- valid state
11: ELSEIF o.status='DELIVERED' and
12: n.status = 'COMPLETED' THEN
13: -- valid state
14: ELSE
15: SIGNAL SQLSTATE '80001' ('Invalid State Transition');
16: END IF;
17: END

În acest caz, declanșatorul este numit verify_state ș​ i este un declanșator care se


activează înainte de orice actualizări din tabelul orders_t. O altă diferență este că
face referire la valoarea existentă (veche), calificându-l cu "o" și noua valoare
folosind 'n'.
Modul în care tranzițiile de stare sunt verificate în liniile de 5 până la 16 este simplă.
În cazul în care tranziția între stări nu este acceptată, presupunem că este o eroare
și este generată o eroare de aplicație cu mesajul "​Invald State Transition​", iar
operația este respinsă. Desigur, logica ar fi putut fi scrisă sub forma:

IF NOT((o.status='PENDING' and n.status IN ('SHIPPED','CANCELLED'))


OR
(o.status='SHIPPED' and n.status = 'DELIVERED' OR
(o.status='DELIVERED' and n.status = 'COMPLETED')) THEN
SIGNAL SQLSTATE '80001' ('Invalid State Transition')
END IF;

Declanșatori DELETE
Pentru ultima regulă de afaceri, vom ilustra un declanșator de stergere:
Regula este urmatoarea:
3a) "O comandă nu poate fi ștearsă dacă nu a fost anulată"
3b) "Comenzile șterse sunt înregistrate în scopuri de audit"

Regula 3a este rezolvată de următorul declansator:

1 : CREATE TRIGGER restrict_delete


2 : NO CASCADE BEFORE DELETE ON orders_t
3 : REFERENCING OLD AS o
4 : FOR EACH ROW MODE DB2SQL
5 : WHEN (o.status <> 'CANCELLED')
6: SIGNAL SQLSTATE '80003' ('Cannot Delete an order that has not
been cancelled')

Declansatori de tip AFTER


Pentru regula 3b, vom utiliza un declanșator de tip after

1 : CREATE TRIGGER log_delete


2 : AFTER DELETE ON orders_t
3 : REFERENCING OLD AS o
4 : FOR EACH ROW MODE DB2SQL
5: INSERT INTO delete_log_t VALUES (
'Order #' || CHAR (o.order_id) ||
'Was deleted on ' || CHAR(CURRENT TIMESTAMP));

Principala diferență între ultimii declansatori si cei dinainte este absenta


instructiunilor BEGIN ATOMIC și END, care nu sunt necesare dacă vrem să facem
doar o singură instrucțiune SQL în declanșator. Trigger-ul va fi activat de fiecare
dată când are loc o ștergere și în cazul în care starea definită prin clauza WHEN este
adevărată.

Testarea Regulilor
Pentru a testa regula de business 1, vom insera două comenzi. Linia de credit
pentru acest client este de doar 100 $, și astfel primul ordin reușește, în timp ce al
doilea ordin eșuează. (Valoarea fiecărei comenzi este de 90 $.)

Insert into orders_t values (nextval for ord_seq, 1, 1, 9, 10.0,


'PENDING')
Insert into orders_t values (nextval for ord_seq, 1, 1, 9, 10.0,
'PENDING')

Regula de afaceri 2 se poate testa plecând de la acțiunea anterioară. Având în


vedere tranzițiile noastre de stare valabile pentru o comandă (de mai sus), un ordin
care este "PENDING" poate fi "SHIPPED". Odată ce o comandă a fost expediată,
acesta nu mai poate fi anulată. Prima actualizare de mai jos va reuși, în timp ce a
doua va eșua.
Update orders_t set status='SHIPPED' where order_id=1
Update orders_t set status='CANCELLED' where order_id=1

Pentru a testa regula de afaceri 3, putem încerca pur și simplu să ștergem ordinul
pe care tocmai l-am procesat. Următoarea declarație de ștergere va eșua, deoarece
starea sa nu este "ANULAT". Pentru că declanșatorul înainte de a
eșuat,declansatorul l ȘTERGE DUPA (care se conectează o acțiune de ștergere) nu
este activat.
Delete from orders_t where order_id=1

Pentru a putea testa declansatorul de stergere vom proceda in felul urmator:

Insert into orders_t values (nextval for ord_seq, 1, 1, 1, 10.0,


'PENDING')
Update orders_t set status='CANCELLED' where order_id=(prevval for
ord_seq)
Delete from orders_t where order_id=(prevval for ord_seq)
Select * from delete_log_t

Sfaturi de performanță

● Declanșatorii de tip BEFORE ar trebui folosiți pentru a modifica valorile


furnizate de către utilizatori sau pentru a genera noi valori, cum ar fi cheile
primare. Încercarea de a modifica rândurile din tabelele de tranziție într-un
declanșator de tip INSERT. .
● DB2 beneficiază de un motor puternic relațional. Cu toate acestea, în
prezent, nu optimizează logica procedurală (de control), la fel de bine ca alte
instrucțiuni SQL.De exemplu declanșatorul verify_credit ​poate fi rescris după
cum urmează:

1 : CREATE TRIGGER verify_credit


2 : NO CASCADE BEFORE INSERT ON orders_t
3 : REFERENCING NEW AS n
4 : FOR EACH STATEMENT MODE DB2SQL
5 : WHEN ((SELECT SUM(price * quantity) FROM orders_t
6: WHERE cust_id = n.cust_id
7: AND status NOT IN ('COMPLETED', 'CANCELLED'))
8: + n.price * n.quality
9: > (SELECT credit FROM customer_t WHERE
cust_id=n.cust_id))
10: SIGNAL SQLSTATE '80000' ('Order Exceeds credit line')

● buclele WHILE sunt aceptate. Sintaxa este descrisă în SQL Reference.


● GET DIAGNOSTICS <var: int> = ROW_COUNT poate fi folosit pentru a
determina câte rânduri au fost afectate de cea mai recentă actualizare,
ștergere sau inserare efectuată de un declanșator.
● Sintaxa SELECT .... INTO nu este acceptată. Pentru a selecta mai multe
coloane în mai multe variabile, se utilizează SET (x, y) = (SELECT x_col,
y_coll FROM MyTable).
● A se evita declanțatorii recursivi. Un declanșator recursiv este un declanșator
care contine in corpul său acelasi tip de instrucțiune care il declanșează. De
exemplu, în cazul în care am definit un declanșator de tip DELETE pentru
MyTable , iar corpul declanșatorului conține, de asemenea, o instrucțiune
DELETE privind MyTable, este un declanșator recursiv și poate duce la
probleme în cazul în care nu este codificat cu atenție. Dacă aveți nevoie să
utilizați recursivitate,a se limita la o singură iterație.
● In cazul în care mai mult de un declanșator este definit pentru un tabel de
exemplu , sunt definiți doi declanșatori INSERT BEFORE), ei vor fi
executați în ordinea în care acestea au fost creați Desigur, declanșatoarele de
tip BEFORE sunt întotdeauna activate înainte de a declanșatoarele de tip
AFTER, indiferent de ordinea creării. De asemenea, alte constrângeri (cheie
primară / constrângeri de cheie străină, constrângeri unice și constrângeri de
verificare) care pot exista în tabel sunt verificate după declanșatoarele de tip
BEFORE și înainte după declanșatorii AFTER..