Sunteți pe pagina 1din 25

Capitolul 16.

Funcii, proceduri i pachete


stocate
Dac procedura sau funcia reprezint noiuni vehiculate de multe decenii de
informaticienii cu sau fr acte n regul, asocierea acestora cu persistena (bazele
de date) a fost consacrat de SGBD-urile deceniului al noulea din precedentul
secol. Stocarea se refer la nuntrul bazei, la dicionarul de date sau catalogul
sistemului, cum i se spune la case (de soft) mai mari. Dei puternic impregnate cu
SQL, funciile i procedurile stocate sunt, dup cum bine le zice numele, cam
procedurale, depinznd ntr-o msur decisiv de extensiile proprietare ale fiecrui
SGBD. i termenul de procedur nu e tocmai n regul deoarece partea se regsete
n ntreg, adic procedurile stocate sunt, n general, de trei tipuri: funcii, proceduri i
declanatoare. Mai nimerit ar fi termenul module stocate, dar dac nu am fost prea
pedani pn n acest capitol, ce rost mai are acum.
n acest capitol ne ocupm de funcii, proceduri i pachete (pachete n sens
oraclist), iar n capitolul urmtor vom trata declanatoarele (trigger-ele). Cu acest
capitol ieim mult de sub incidena standardelor SQL i intrm pe proprietile
serverelor de baze de date. De peste zece ani, standardul SQL are o component
special Persistent Stored Modules care ncearc s fie un numitor comun al
dialectelor procedurale ale serverelor BD. Procesul de armonizare este vizibil, ns
diferenele dintre sistemele de gestiune a bazelor de date sunt nc semnificative,
uneori uriae.
Nu mi-am propus, i dealtfel nici nu m pricep, s tratez n extenso subiectul
procedularitii n cele patru servere. ns urmtoarele cteva exemple pot
constitui un bun punct de plecare pentru cei care vor s fac performan. Ca
lucrri de referin v-a recomanda, printre altele:
pentru DB2 limbajul SQL PL: [Mullins2004], [Janmohamed s.a. 2004],
[Birchall 2007], [Bedoya s.a. 2004];
pentru Oracle limbajul PL/SQL: [Lakshman 2003], [Greenwald s.a.
2005], [Price 2008], [Kyte 2005], [Feuerstein 2007], [Urman s.a. 2004],
[McLaughlin 2008];
pentru PostgreSQL limbajul PL/pgSQL: [Geschwinde & Schnig
2001], [Matthew & Stones 2005], [Douglas & Douglas 2005];
pentru MS SQL Server limbajul T-SQL: [Bain s.a. 2003], [Watt 2007],
[underi 2006], [Ben-Gan s.a. 2006], [Vieira 2007].
16.1. Funcii stocate
Funciile stocate reprezint un ingredient al tuturor SGBD-urilor semnificative
de astzi. De obicei, n programare, se spune c o funcie este un bloc/program
706 Capitolul 16
care, la invocare, returneaz (furnizeaz) o valoare. Valoarea poate fi una simpl,
de tip nregistrare sau seturi de nregistrri. Pentru variaie, modificm ordinea de
intrare n scen a celor patru servere BD n abordarea subiectului. Vom ncepe cu
PostgreSQL-ul, vom continua cu Oracle i SQL Server i vom ncheia cu DB2.
16.1.1. Primele funcii stocate n PostgreSQL
n PostgreSQL funciile stocate pot fi create n mai multe limbaje: Perl, C etc. Pe
noi ne intereseaz doar dou dintre acestea, SQL i PLpg/SQL. Cele mai simple
sunt cele SQL. De obicei acestea sunt simple interogri parametrizate care
returneaz o valoare. Spre exemplu, n listing 16.1 apar dou comenzi (CREATE
FUNCTION) prin care se creaz funcii. Prima, denumit f_proctva, primete, la
invocare (apelare) valoarea unui cod de produs i furnizeaz procentul su de
TVA. A doua, f_denpr, primete un cod de produs i furnizeaz denumirea
acestuia.
Listing 16.1. Primele dou funcii stocate PostgreSQL redactate n limbajul SQL
-- F_PROCTVA -- pentru un cod de produs returneaza procentul de TVA al produsului respectiv
CREATE OR REPLACE FUNCTION f_proctva (codpr_ NUMERIC) RETURNS NUMERIC
AS $$
SELECT proctva FROM produse WHERE codpr = $1
$$ LANGUAGE SQL ;

-- F_DENPR - primeste un cod de produs si returneaza denumirea
CREATE OR REPLACE FUNCTION f_denpr (codpr_ produse.codpr%TYPE)
RETURNS produse.denpr%TYPE AS $$
SELECT denpr FROM produse WHERE codpr = $1 ;
$$ LANGUAGE SQL ;


Ambele funcii folosesc ca parametru de intrare CodPr_ care este definit direct,
NUMERIC, n prima funcie i indirect - PRODUSE.CodPr%TYPE -, n a doua.
Acest din urm mod de specificare a tipului unui parametru este poate mai lung,
ns mai elegant. Dac, ulterior, s-ar modifica tipul atributului CodPr din
PRODUSE (din NUMERIC n VARCHAR (nu m ntrebai de ce)), ar trebui
modificate toate funciile stocare care prezint parametri de genul CodPr_, definii
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 707
ca NUMERIC
1
, n timp ce funciile n care parametrii au fost definii indirect nu
sufer modificri (eventual, ar trebui recompilate). De remarcat c, n corpul
funciei, referirea la parametrul codpr_ se realizeaz prin $1 (dac funcia ar fi avut
trei parametri, referirea lor s-ar fi fcut prin $2 i $3)
2
.
Lansarea n execuie a celor dou comenzi CREATE FUNCTION poate fi
realizat precum n figura 16.1, dei o variant mai simpl ar fi lucrul asistat (chiar
dac n pgAdmin lucrul asistat nu este ntotdeauna o bagatel).

Figura 16.1. Crearea a dou funcii (SQL) simple (de tot) n PostgreSQL












1
E un fel de mini-problem a anului 2000 (Y2K) de care cei mai n vrst dintre dvs. ai auzit n
tineree.
2
La drept vorbind, numele parametrului poate lipsi, doar tipul su fiind important.
708 Capitolul 16
Dup lansarea acestor comenzi, n pgAdmin, cele dou funcii pot fi vizualizate
ca n figura 16.2 (nu uitai de remprosptarea Refresh - meniului arborescent).
Odat creat, o funcie poate fi apelat att din alt funcie, ct i dintr-o interogare
SQL:
SELECT nrfact, f_denpr(codpr) AS denpr, cantitate, pretunit,
cantitate * pretunit AS Val_fara_tva,
cantitate * pretunit * f_proctva(codpr) AS tvalinie,
cantitate * pretunit * (1 + f_proctva(codpr)) AS Val_cu_TVA_linie
FROM liniifact

n PostgreSQL o funcie SQL poate conine mai multe comenzi SQL, ns valoa-
rea, linia sau setul de linii sunt returnate pe baza ultimei comenzi SQL. De
asemenea, dac valoarea returnat declarat prin clauza RETURNS este una simpl
(o valoare sau o nregistrare), iar ultimul SELECT al funciei extrage dou sau mai
multe ntregistrri dintr-o tabel, nu se va declana vreo eroare (precum n Oracle
PL/SQL), ci se va returna prima linie din setul de nregistrri, ceea ce poate crea
confuzie (mai ales n lipsa opiunii ORDER BY n acest ultim interogare a
funciei).

SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 709
Figura 16.2. Afiarea celor dou funcii n pgAdmin
S lum, spre exemplu, funcia f_test01 (listing 16.2) alctuit din trei fraze
SELECT destul de anapoda, care extrag, pe rnd, nregistrrile din tabelele
CLIENI, PERSOANE i FACTURI (ultima ar extrage numai valorile atributului
NrFact din toate liniile tabelei FACTURI):
Listing 16.2. Funcia SQL (din PostgreSQL) f_test01
CREATE OR REPLACE FUNCTION f_test01 () RETURNS NUMERIC AS $$
SELECT * FROM clienti ;
SELECT * FROM persoane ;
SELECT NrFact FROM facturi ;
$$ LANGUAGE SQL ;


E oarecum curios c sintaxa aceastei funcii este acceptat n PostgreSQL.
ntruct valoarea declarat n clauza RETURNS este simpl (numeric), apelul
funciei nu se soldeaz cu eroare, ci cu vizualizarea unui numr de factur vezi
figura 16.3.

Figura 16.3. O funcie Postgre(SQL) ciudat i rezultatul su
Limbajul "clasic" de redactare a funciilor stocate complexe este, n PostgreSQL,
PL/pgSQL. Sintaxa sa este similar, n multe privine, limbajului Oracle PL/SQL.
Relum cele dou funcii anterioare i le rescriem pe sintaxa PL/pgSQL vezi
listing 16.3.
Listing 16.3. Primele dou funcii trecute n PLpgSQL
-- F_PROCTVA -- pentru un cod de produs returneaza procentul de TVA al produsului
CREATE OR REPLACE FUNCTION f_proctva2 (codpr_ produse.codpr%TYPE)
RETURNS produse.ProcTVA%TYPE AS $$
DECLARE
v_proc produse.proctva%TYPE ;
BEGIN
710 Capitolul 16
SELECT proctva INTO v_proc FROM produse WHERE codpr = codpr_ ;
RETURN v_proc ;
END ;
$$ LANGUAGE plpgsql ;
-------------------------------------------------------------------------
-- F_DENPR - primeste un cod de produs si returneaza denumirea
CREATE OR REPLACE FUNCTION f_denpr2 (codpr_ produse.codpr%TYPE)
RETURNS produse.denpr%TYPE AS $$
DECLARE
v_denpr produse.denpr%TYPE ;
BEGIN
SELECT denpr INTO v_denpr FROM produse WHERE codpr = codpr_ ;
RETURN v_denpr ;
END ;
$$ LANGUAGE plpgsql;


Un bloc (program) scris n limbajul PL/pgSQL poate avea trei seciuni; prima
(opional) este declarativ - ncepe cu DECLARE; a doua este cea executabil -
ncepe cu BEGIN; a treia este tot opional i este destinat tratrii eventualelor
erori ce pot fi declanate de funcie, erori denumite excepii. Spre deosebire de
funciile redactate n limbajul SQL, n PL/pgSQL parametrii sunt referii prin
numele lor.

Ca i n alte limbaje, putem s redactm funcii care s suplineasc oferta
sistemului. Spre exemplu, n PostgreSQL nu exist funciile LEFT i RIGHT (din
SQL Server) care furnizeaza primele, respectiv ultimele n caractere dintr-un ir.
Aa c putem redacta noi dou funcii pe care le vom numi LEFT_ i RIGHT_ (am
adaugat un underscore tocmai pentru a nu intra n conflict cu o eventual
viitoare versiune PostgreSQL care va implementa cele dou funcii. Iat aceste
dou funcii n listingul 16.4.
Listing 16.4. Funciile left_ i right_ (n PLpgSQL)
-- Functia LEFT_ extrage primele cite_ caractare dintr-un sir_
CREATE OR REPLACE FUNCTION LEFT_ (sir_ TEXT, cite_ INTEGER) RETURNS TEXT
AS $$
DECLARE
v_sir_returnat TEXT ;
BEGIN
v_sir_returnat := SUBSTR(sir_, 1, cite_) ;
RETURN v_sir_returnat ;
END ;
$$ LANGUAGE plpgsql ;

-- Functia RIGHT_ extrage ultimele cite_ caractare dintr-un sir_
CREATE OR REPLACE FUNCTION RIGHT_ (sir_ TEXT, cite_ INTEGER) RETURNS TEXT
AS $$
DECLARE
v_sir_returnat TEXT ;
BEGIN
v_sir_returnat := SUBSTR(sir_, LENGTH(sir_) - cite_ + 1, cite_) ;
RETURN v_sir_returnat ;
END ;
$$ LANGUAGE plpgsql ;
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 711

Ilustrarea secvenelor alternative i iterative n PL/pgSQL ne mpinge la
redactarea unei funcii care s rezolve o problem destul de important
calcularea scadenei facturilor peste un numr de zile lucrtoare. Funcia de
numete f_data_scadenta (vezi listing 16.5).
Listing 16.5. Funcia PLpgSQL f_data_scadenta
CREATE OR REPLACE FUNCTION f_data_scadenta (
datai_ DATE, nrzile_scadenta_ NUMERIC) RETURNS DATE AS $$
DECLARE
zi_crt DATE ;
v_gata BOOLEAN := FALSE ;
v_nr_zile_lucratoare NUMERIC := 0 ;
BEGIN
-- scadenta este peste NRZILE_SCADENTA_ (lucratoare)
zi_crt := datai_ ;
WHILE NOT v_gata LOOP
zi_crt := zi_crt + 1 ;
IF RTRIM(TO_CHAR(zi_crt, 'DAY')) NOT IN ('SATURDAY','SUNDAY') THEN
v_nr_zile_lucratoare := v_nr_zile_lucratoare + 1 ;
IF v_nr_zile_lucratoare >= nrzile_scadenta_ THEN
EXIT ;
END IF ;
END IF ;
END LOOP ;
RETURN zi_crt ;
END ;
$$ LANGUAGE plpgsql ;

Dac presupunem c scadena fiecrei facturi este peste 19 zile lucrtoare de la
data ntocmirii, atunci interogarea devine foarte simpl:
SELECT NrFact, DataFact,
f_data_scadenta (DataFact, 19) AS "Scadenta (19 zile lucratoare)"
FROM facturi
iar rezultatul este cel din figura 16.4.

Figura 16.4. Scaden peste 19 zile lucrtoare
712 Capitolul 16
Acum ne propunem s afim n dreptul fiecrei facturi, sub form de ir de
caractere lista produselor (denumirile lor) care o alctuiesc, ca n figura 16.5. O
variant de rezolvare a problemei se bazeaz pe dou funcii numite
f_codpr_liniefact i f_produse_din_factura_v1 vezi listingul 16.6. Prima primete doi
parametri, un numr de factur (nrfact_) i un numr de linie (linie_) i returneaz
codul produsului de pe linia/factura respective. n cazul n care nu exist linia cu
numrul linie_ n factura nrfact_, funcia returneaz valoarea 0. A doua -
f_produse_din_factura_v1 construiete irul ce conine denumirile produselor din
factura nrfact_ printr-o bucl executat de maximum 99 ori, aceasta deoarece
presupunem c o factur conine cel mult 99 de linii (ceea ce e destul de
improbabil). Cum liniile din toate facturile sunt dispuse consecutiv, n momentul n
care se depisteaz o linie inexistent, se iese forat (EXIT) din bucl, aa c nu
trebuie s ne facem griji pentru mersul n gol al iteraiei.
Listing 16.6. Funciile f_codpr_liniefact i f_produse_din_factura_v1
CREATE OR REPLACE FUNCTION f_codpr_liniefact
(nrfact_ liniifact.nrfact%TYPE, linie_ liniifact.linie%TYPE)
RETURNS liniifact.codpr%TYPE AS $$
DECLARE
v_codpr liniifact.codpr%TYPE := 0 ;
BEGIN
SELECT CodPr INTO v_codpr FROM liniifact WHERE NrFact=nrfact_ AND Linie=linie_ ;
RETURN v_codpr ;
END ;
$$ LANGUAGE plpgsql ;

-----------------------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION f_produse_din_factura_v1
(nrfact_ facturi.nrfact%TYPE) RETURNS TEXT AS $$
DECLARE
v_sir TEXT := '' ;
BEGIN
FOR i IN 1..99 LOOP
IF f_codpr_liniefact (CAST(nrfact_ AS INTEGER), CAST( i AS SMALLINT)) > 0 THEN
v_sir := v_sir || ' * ' ||
f_denpr(f_codpr_liniefact (CAST (nrfact_ AS INTEGER), CAST (i AS SMALLINT)) ) ;
ELSE
EXIT ;
END IF ;
END LOOP ;
RETURN v_sir ;
END ;
$$ LANGUAGE plpgsql ;


Pentru a prentmpina erorile legate de nepotrivirile de tip ntre parametrii
specificai la apelarea funciei f_codpr_liniefact i cei declarai n corpul funciei
respective, folosim funcia CAST. Extragerea denumirii unui produs pe baza
codului su ne prilejuiete folosirea funciei discutate la nceputurile paragrafului,
f_denpr. Raportul din figura 16.5 este obinut acum prin interogarea:
SELECT facturi.NrFact, facturi.DataFact,
f_produse_din_factura_v1(facturi.NrFact) AS Lista_produse
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 713
FROM facturi ORDER BY 1

Figura 16.5. Produsele din componena fiecrei facturi

16.2. Cursoare
SQL lucreaz cu seturi de nregistrri. Noiunea de nregistare/linie curent nu
exist nici n modelul relaional, nici n SQL. Cu toate acestea, aplicaiile complexe
necesit proceduri i funcii n care prelucrarea pe seturi nu este suficient, orict
de puternice ar fi dialectele SQL n materie de subconsultri, opiuni OLAP, ierar-
hii/recursivitate etc. Cursorul reprezint un instrument ideal pentru prelucrrile
linie-cu-linie. De asemenea, n aplicaiile client/server i web folosind funcii
bazate pe cursoare fluxul datelor n reea poate fi optimizat sensibil.
714 Capitolul 16

Figura 16.15. Prima variant de lucru cu un cursor
Un cursor reprezint un set de nregistrri obinut printr-o interogare i stocat
undeva n memorie, set ce poate fi parcurs secvenial, uneori ntr-o singur direcie
(de la prima nregistrare la ultima), alteori n ambele direcii, fiecare nregistrare
pstrnd, n unele cazuri, legtura cu linia tabelei din care provine.
Operaiunile derulate n lucrul cu un cursor sunt:
Definirea/declararea;
Deschiderea;
ncarcrea uneia sau mai multor nregistrri din cursor;
nchiderea.
Chiar dac este particularizat pe sintaxa Oracle PL/SQL, figura 16.15
ilustreaz destul de bine prima (i cea mai complicat) modalitate de folosire a
unui cursor. Definirea se face n zona declarativ. Variabila rec_cursor este folosit
la stocarea nregistrrii curente la trecerea prin cursor.
n zona executabil a blocului mai nti se deschidere cursorul (OPEN).
Deschiderea echivaleaz cu execuia interogrii de la declararea cursorului i
stocarea setului de linii obinut prin interogare ntr-o zon de memorie. O comand
FETCH ncarc urmtoarea nregistrare din cursor n variabila rec_cursor. Structura
repetitiv WHILE c_cursor%FOUND LOOP ... END LOOP asigur parcurgerea
tuturor nregistrrilor cursorului, iar comanda CLOSE face curenie, prin
nchidere, spaiul de memorie alocat fiind eliberat.
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 715

Figura 16.16. A doua variant de lucru cu un cursor
A doua variant de lucru, a crei logic este cea din figura 16.16 este mai simpl
ntruct:
cursorul se declar ad-hoc n zona executabil;
variabila care pstreaz nregistrarea curent din cursor nu trebuie
definit n zona DECLARE;
cursorul nu mai trebuie deschis i nchis explicit;
deplasarea la nregistrarea urmtoare este automat.

Exist i o treia variant, derivat din a doua, i mai simpl dect aceasta, n
care declararea cursorului se face direct n structura iterativ de parcurgere a
nregistrrilor vezi figura 16.17.

Figura 16.17. A treia variant de lucru cu un cursor
Dei sunt cteva diferene de sintax, pe care le vom discuta pe scurt n cele ce
urmeaz, logica celor trei variante este implementat n toate cele patru limbaje ale
716 Capitolul 16
serverelor BD din aceast lucrare. De asemenea, n paragrafele dedicate proceduri-
lor (16.5.1 - 16.5.4) vom completa discuia referitoare la cursoarele explicite.
16.2.1. Cursoare n PostgreSQL PL/pgSQL
ncepem exemplificarea cursoarelor printr-o funcie ce rezolv o problem din
paragraful 16.1.1 cea care i propunea s obin raportul din figura 16.5. n locul
celor dou funcii din listingul 16.7, putem folosi una singur - f_produse_din_fac-
tura_v2 pe care o prezentm n listingul 16.22. Ca element de noutate apare i o
variabil de tip RECORD, rec_linii. Aceast este grozav de versatil, deoarece poate
stoca o linie din orice tabel, neavnd o structur predefinit. n cazul nostru,
rec_linii va stoca o linie a cursorului ad-hoc, definit prin SELECT-ul din structura
FOR... Practic, cursorul va conine attea nregistrri cte linii sunt n tabela LINII-
FACT pentru factura cu numrul nrfact_.
Listing 16.22. Funcia f_produse_din_factura_v2
CREATE OR REPLACE FUNCTION f_produse_din_factura_v2 (nrfact_ facturi.nrfact%TYPE)
RETURNS TEXT
AS $$
DECLARE
rec_linii RECORD ;
v_sir TEXT := '' ;
BEGIN
FOR rec_linii IN (SELECT CodPr FROM liniifact WHERE NrFact = nrfact_ ) LOOP
v_sir := v_sir || ' * ' || f_denpr(rec_linii.CodPr) ;
END LOOP ;
RETURN v_sir ;
END ;
$$ LANGUAGE plpgsql ;


Firete c apelul funciei nu pune absolut nicio problem:
SELECT facturi.NrFact, facturi.DataFact,
f_produse_din_factura_v2(facturi.NrFact) AS Lista_produse
FROM facturi
ORDER BY 1

Ne propunem n continuare s crem funciile stocate care s returneze expresi-
ile cheilor primare i strine, indiferent de numrul atributelor din cheile compuse
(n paragraful 15.1.1 am presupus c pot fi maximum patru atribute ntr-o cheie
compus). Funcia f_cheie_primar din listing 16.23 extrage mai nti numele
restriciei de tip cheie primar pentru tabela dat. tim c o tabel nu poate avea
mai multe chei primare, aa c nu avem nicio grij la interogarea tabelei virtuale
dicionar information_schema.table_constraints. Cursorul va prelua toate nregistrrile
extrase din view-ul information_schema.key_column_usage pentru restricia cu
numele stocat n variabila v_nume_restrictie.
Listing 16.23. Funcia f_cheie_primara
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 717
CREATE OR REPLACE FUNCTION f_cheie_primara (tabela_ TEXT)
RETURNS TEXT AS $$
DECLARE
v_nume_restrictie TEXT ;
v_sir VARCHAR(200) := '(' ;
rec_atribute RECORD ;
BEGIN
SELECT constraint_name INTO v_nume_restrictie
FROM information_schema.table_constraints
WHERE UPPER(table_name)=UPPER(tabela_) AND constraint_type='PRIMARY KEY' ;

FOR rec_atribute IN ( SELECT column_name, ordinal_position
FROM information_schema.key_column_usage
WHERE constraint_name = v_nume_restrictie
ORDER BY ordinal_position) LOOP
v_sir := v_sir || CASE WHEN LENGTH(v_sir)=1 THEN '' ELSE ',' END
|| rec_atribute.column_name ;
END LOOP ;
RETURN v_sir || ')';
END ;
$$ LANGUAGE plpgsql ;


La fiecare nregistrare a cursorului, variabila v_sir va fi incrementat cu
atributul corespunztor liniei curente a cursorului. Testm funcia pentru tabela
PERSCLIENTI (rezultatul fiind cel din figura 16.18) :
SELECT f_cheie_primara ('persclienti')

Figura 16.18. Componena cheii primare a tabelei PERSCLIENTI
Cutnd un pretext pentru folosirea n tandem a dou cursoare, ne propunem
s construim o funcie care s afieze toate expresiile cheilor alternative pentru o
tabel dat. Diferena principal fa de problema anterioar este c o tabel poate
avea mai multe chei alternative (i doar o singur cheie primar). Este i motivul
pentru care funcia (vezi listing 16.24) se numete f_chei_alternative.
Listing 16.24. Funcie PL/pgSQL ce folosete dou cursoare
CREATE OR REPLACE FUNCTION f_chei_alternative (tabela_ TEXT)
RETURNS TEXT AS
$$
DECLARE
v_sir TEXT := '' ;
rec_chei_alternative RECORD ;
rec_atribute RECORD ;
BEGIN
-- este posibil ca intr-o tabela sa avem mai multe chei alternative, asa ca
-- folosim un prim cursor
FOR rec_chei_alternative IN (SELECT constraint_name
FROM information_schema.table_constraints
WHERE UPPER(table_name)=UPPER(tabela_)
AND constraint_type='UNIQUE') LOOP
718 Capitolul 16
v_sir := v_sir || ' UNIQUE (' ;
-- fiecare cheie alternativa poate fi compusa, asa ca folosim
-- un al doilea cursor
FOR rec_atribute IN ( SELECT column_name, ordinal_position
FROM information_schema.key_column_usage
WHERE constraint_name =
rec_chei_alternative.constraint_name
ORDER BY ordinal_position) LOOP
v_sir := v_sir || CASE WHEN
SUBSTR(v_sir, LENGTH(v_sir)-7,9)='UNIQUE (' THEN '' ELSE ',' END
|| rec_atribute.column_name ;
END LOOP ;
END LOOP ;
RETURN v_sir || CASE v_sir WHEN '' THEN '' ELSE ')' END ;
END ;
$$ LANGUAGE plpgsql ;


Ar trebui ca explicaiile inserate n corpul funciei s uureze nelegerea meca-
nismului de execuie. Partea ce mai interesant este manevrarea variabilei v_sir.
Structura CASE din corpul buclei verific dac atributul curent (rec_atribute.co-
lumn_name) este primul din componena cheii alternative curente (rec_chei_alternati-
ve.constraint_name). Dac nu e primul, trebuie precedat de o virgul.
La capitolul specificiti cursoristice PL/pgSQL, ar trebui adugat c sintaxa
comenzii FETCH este foarte generoas. Astfel, putem ncrc direct ultima
nregistrare din cursor (prin FETCH LAST) sau rencrca precedenta nregistrare
(prin FETCH RELATIVE -1). Mai mult, exist comanda MOVE prin care ne putem
deplasa n cursor dup bunul plac, fr a ncrca noua nregistrare curent.
16.3. Funcii ce furnizeaz seturi de nregistrri
Un debueu clasic al funciilor stocate este obinerea de rapoarte dintre cele
mai diverse. Specific acestor funcii este c returneaz seturi de nregistrri dintr-o
tabel/interogare, set furnizat interfeei sau logiciii aplicaiei de ctre serverul BD.
Vom vedea c sunt diferene semnificative, ba chiar, mai mult, presupuii outsideri
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 719
PostgreSQL i SQL Server sunt, pe alocuri mai generoi dect greii DB2 i
Oracle.
16.3.1. Funcii ce returneaz seturi de nregistrri n PostgreSQL
ncepem cu o funcie simpl f_facturi_filtrate_cronologic - care opereaz o
selecie a liniilor tabelei FACTURI pentru un interval calendaristic vezi listing
16.34. Elegana funciei provine din clauza RETURNS SET OF... care poate indica
structura (atributele) unei tabele, tabele virtuale sau a unui tip-utilizator
3
. Funcia
nu este redactat n PL/pgSQL, ci n SQL, avnd doi parametri de tip DATE care
sunt delimitatorii intervalului..
Listing 16.34. Funcia SQL (din PostgreSQL) f_facturi_filtrate_cronologic
CREATE OR REPLACE FUNCTION f_facturi_filtrate_cronologic
(data_initiala DATE, data_finala DATE)
RETURNS SETOF facturi AS
$$
SELECT * FROM facturi WHERE datafact BETWEEN $1 AND $2
$$ LANGUAGE SQL ;


Odat compilat, funcia poate fi invocat returnnd un rezultat ca orice tabel
sau tabel virtual:
SELECT *
FROM f_facturi_filtrate_cronologic (DATE'2007-09-14', CURRENT_DATE) t

Similar, putem crea o funcie care returneaza numai facturile unui anumit client
(cu un cod specificat) vezi listing 16.35.
Listing 16.35. Funcia SQL (din PostgreSQL) f_facturile_unui_client
CREATE OR REPLACE FUNCTION f_facturile_unui_client
(codcl_ clienti.codcl%TYPE) RETURNS SETOF facturi AS
$$
SELECT * FROM facturi WHERE codcl = $1












3
Crearea i utilizarea tipurilor utilizator constituie subiectul capitolului 19.
720 Capitolul 16
$$ LANGUAGE SQL ;


Cele dou funcii pot fi folosite n aceeai interogare pentru aplicarea a dou
filtre. Spre exemplu, dorim extragerea tuturor facturilor emise clientului 1001 dupa
5 august 2007:
SELECT * FROM f_facturile_unui_client (1001) t
INTERSECT
SELECT *
FROM f_facturi_filtrate_cronologic (DATE'2007-08-05', CURRENT_DATE) t

Funcia urmtoare f_facturi_filtrate redactat tot n SQL, prezint trei
parametri: un cod de client, o dat iniial i o dat final care delimiteaz un
interval calendaristic vezi listing 16.36. Setul de nregistrri returnate reprezint
facturile destinate clientului dat (parametru) ntocmite n intervalul definit de cele
dou date calendaristice. Dac la invocarea funciei, vreunul dintre parametri este
NULL se consider c nu se mai face filtrarea liniilor dup parametrul respectiv.
Astfel, cnd codul clientului este NULL, atunci se vor extrage facturile pentru toi
clienii. Dac data iniial este NULL, atunci se vor extrage facturile de la
nceputurile bazei de date (s zicem, 1 ian. 2005).
Listing 16.36. Funcia SQL (din PostgreSQL) f_facturi_filtrate
CREATE OR REPLACE FUNCTION f_facturi_filtrate (
codcl_ clienti.codcl%TYPE, data_initiala DATE, data_finala DATE )
RETURNS SETOF facturi AS
$$
SELECT * FROM facturi
WHERE codcl = COALESCE($1, codcl)
AND datafact BETWEEN COALESCE($2, DATE'2005-01-01')
AND COALESCE($3, DATE'2010-12-31')
$$ LANGUAGE SQL ;


Iat i cteva modaliti de folosire a funciei pentru a afla informaii precum:
facturile clientului 1001 emise ntre 14 septembrie 2007 i data curent:
SELECT *
FROM f_facturi_filtrate(1001, DATE'2007-09-14', CURRENT_DATE) t

toate facturile clientului 1001:
SELECT *
FROM f_facturi_filtrate(1001, NULL, NULL) t

facturile emise pe 7 august 2007:
SELECT *
FROM f_facturi_filtrate(NULL, DATE'2007-08-07', DATE'2007-08-07') t

SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 721
toate facturile emise:
SELECT *
FROM f_facturi_filtrate(NULL, NULL, NULL) t

Dup cum sugeram ceva mai sus, n caz c setul de ntregistrri returnat nu are
exact structura unei tabele, trebuie creat un tip tip_nou i folosit clauza
RETURNS SET OF tip_nou, dup cum vom vedea n capitolul 19.
16.4. Funcii folosite ca valori implicite i restricii
la nivel de atribut/nregistrare
O aplicabilitate ceva mai exotic a funciilor stocate ine de asocierea lor unor
valori implicite (clauze DEFAULT) i reguli de validare (clauze CHECK). Numai
PostgreSQL i SQL Server permit lucrul acesta, aa c doar la acestea ne vom referi
n cele de urmeaz.
S ncepem printr-o funcie PL/pgSQL ce furnizeaz cel mai frecvent cod de
client din facturi vezi listing 16.44. Din capitolele anterioare tim c putem grupa
facturile dup valorile CodCl i extrage cea mai mare valoare a numrtorii prin
clauza ORDER BY COUNT(*) DESC LIMIT 1.
Listing 16.44. Funcia PL/pgSQL ce urmeaz a furniza valoarea implicit a FACTURI.CodCl
CREATE OR REPLACE FUNCTION f_vi_codcl() RETURNS clienti.codcl%TYPE AS
$$
DECLARE
v_codcl clienti.codcl%TYPE ;
BEGIN
SELECT codcl INTO v_codcl FROM facturi GROUP BY codcl
ORDER BY COUNT(*) DESC LIMIT 1;

-- daca nu e nicio factura introdusa, functia returneaza cel mai mic cod de client
IF v_codcl IS NULL THEN
SELECT MIN(CodCl) INTO v_codcl FROM clienti ;
END IF ;
RETURN v_codcl ;
END ;
$$ LANGUAGE plpgsql ;


Asocierea acestei funcii cu valoarea implicit a atributului FACTURI.CodCl se
realizeaz prin comanda ALTER TABLE:
ALTER TABLE facturi ALTER COLUMN CodCl SET DEFAULT f_vi_codcl() ;

Testm valabilitatea funciei insernd o factur pentru care nu specificm codul
clientului:
INSERT INTO facturi (NrFact, DataFact) VALUES (7050, CURRENT_DATE)

722 Capitolul 16
La afiarea acestei linii proaspt inserate vezi figura 16.23 observm c
lucrurile au decurs conform ateptrilor, clientul cruia i-am trimis cele mai multe
facturi avnd codul 1001.

Figura 16.23. Verificarea funciei-valoare implicit
Sintaxa Transact-SQL aceleai funcii este prezentat n listingul 16.45. Interoga-
rea ce furnizeaz prima valoare (cea mai frecvent) a atributului CodCl_ n tabela
FACTURI este inclus ntr-o comand SET. La fel i cea care extrage cel mai mic
cod de client.
Listing 16.45. Funcia T-SQL ce urmeaz a furniza valoarea implicit a FACTURI.CodCl
CREATE FUNCTION dbo.f_vi_codcl ()
RETURNS SMALLINT
BEGIN
DECLARE @codcl SMALLINT
SET @codcl = 0

SET @codcl = ( SELECT TOP 1 CodCl FROM facturi
GROUP BY CodCl ORDER BY COUNT(*) DESC
)
-- daca nu e nicio factura introdusa, functia returneaza cel mai mic cod de client
IF @codcl = 0
SELECT @codcl = (SELECT TOP 1 CodCl FROM clienti)

RETURN @codcl
END


i sintaxa comenzii ALTER TABLE este un pic diferit n T-SQL:
ALTER TABLE facturi ADD CONSTRAINT def_facturi_codcl
DEFAULT dbo.f_vi_codcl () FOR CodCl

n schimb testarea funciei se face ca n PostgreSQL, doar c INSERT-ul nu
poate folosi funcia CURRENT_DATE, ci GETDATE():
INSERT INTO facturi (NrFact, DataFact) VALUES (7050, getdate())

Continum cu o funcie pe care vrem s o asociem unei clauze CHECK regul
de validare la nivel de linie. Probabil c n capitolele 3 i 13 ai fost dezamgii de
srcia clauzei CHECK a comenzilor CREATE/ALTER TABLE. n aplicaiile
economice, deseori trebuie gestionate restricii mult mai complexe dect simplele
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 723
expresii permise de clauza CHECK. Chiar dac soluia general de implementare a
(aproape) tuturor restriciilor o reprezint declanatoarele (triggerele) discutate n
capitolul urmtor, PostgreSQL i SQL Server accept ca n clauzele CKECK, n
locul unui predicat clasic s fie specificat numele unei funcii.
S lum cazul unei funcii ce primete, ca parametru de intrare, valoarea
codului unui client (CodCl_) i furnizeaz TRUE sau FALSE dac restul de plat al
acelui client, adic diferena dintre vnzri i ncasri, este mai mic sau mai mare
dect 175000000 RON. Funcia se numete f_ck_facturi_codcl i este prezentat n
listing 16.46.
Listing 16.46. Funcia PL/pgSQL ce urmeaz a fi clauz CHECK n tabela FACTURI
CREATE OR REPLACE FUNCTION f_ck_facturi_codcl (codcl_ facturi.CodCl%TYPE)
RETURNS BOOLEAN AS
$$
BEGIN
RETURN CASE WHEN
COALESCE((
SELECT SUM(Cantitate * PretUnit * (1 + f_procTVA (lf.CodPr)))
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
WHERE CodCl=codcl_),0) -
COALESCE((
SELECT SUM(Transa)
FROM facturi f INNER JOIN incasfact i
ON f.NrFact=i.NrFact
WHERE CodCl=codcl_
),0) < 175000000 THEN TRUE ELSE FALSE
END ;
END ;
$$ LANGUAGE plpgsql ;


Declararea sa ca regul de validare pentru tabela FACTURI se realizeaz, firesc,
printr-o comand ALTER TABLE:
ALTER TABLE facturi ADD CONSTRAINT ck_facturi_codcl
CHECK (f_ck_facturi_codcl (codcl)) ;

Nu v rmne dect s testai funcionalitatea noii reguli, introducnd o factur
cu o valoare foarte mare. Versiunea Transact-SQL a funciei f_ck_facturi_codcl
reprezint subiectul listing-ului 16.47.
Listing 16.47. Funcia T-SQL ce urmeaz a fi clauz CHECK n tabela FACTURI
CREATE FUNCTION dbo.f_ck_facturi_codcl (@codcl SMALLINT)
RETURNS BIT
BEGIN
RETURN CASE WHEN
COALESCE((
SELECT SUM(Cantitate * PretUnit * (1 + dbo.f_proctva (lf.CodPr)))
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
WHERE CodCl = @codcl),0) -
COALESCE((
SELECT SUM(Transa)
FROM facturi f INNER JOIN incasfact i
724 Capitolul 16
ON f.NrFact=i.NrFact
WHERE CodCl = @codcl
),0) < 175000000 THEN 0 ELSE 1
END
END


Comanda ALTER TABLE pentru asocierea acestei funcii unei clauze CHECK a
tabelei FACTURI are sintaxa SQL Server:
ALTER TABLE facturi WITH CHECK ADD CONSTRAINT ck_facturi_codcl
CHECK (dbo.f_ck_facturi_codcl (CodCl) = 0) ;
16.5. Proceduri stocate
Muli dintre noi tiu din tineree (mcar) c, dac o funcie returneaz ceva, o
procedur face ceva. Definiia nu este dintre cele mai pedagogice, dar are
calitile ei. Fa de funcii, discuia legat de proceduri este mai sumar. Dintre
cele patru servere BD, PostgreSQL-ul nu are proceduri propriu-zise, ci doar funcii
care returneaz VOID.
16.5.1. Proceduri stocate n PostgreSQL
ncepem cu o funcie-procedur care adaug n observaiile (Obs) fiecrei
facturi mesajul Nu are linii ! dac numrul de factur respectiv nu se regsete n
tabela LINIFACT vezi listing 16.48.
Listing 16.48. Funcia-procedur SQL ce actualizeaz valorile atributului Obs
CREATE OR REPLACE FUNCTION f_update_facturi_fara_linii()
RETURNS VOID AS $$
UPDATE facturi SET obs = COALESCE(obs,' ') || 'Nu are linii !'
WHERE nrfact NOT IN (SELECT DISTINCT nrfact FROM liniifact)
$$ LANGUAGE SQL ;


Apelarea procedurii nseam, de fapt, apelarea funciei, n maniera din
paragraful 16.1.1:
SELECT f_update_facturi_fara_linii()
Se poate reproa acestei funcii-proceduri c, la executri repetate, umple
observaiile cu mesaje Nu are linii !, aa c ne putem gndi la o varianta mai
acceptabil vezi listing 16.49.
Listing 16.49. O variant mai rsrit a funciei-procedur
CREATE OR REPLACE FUNCTION f_update_facturi_fara_linii()
RETURNS VOID AS $$
UPDATE facturi SET obs = COALESCE(obs,' ') || 'Nu are linii !'
WHERE nrfact NOT IN (SELECT DISTINCT nrfact FROM liniifact)
AND COALESCE(obs,' ') NOT LIKE '%Nu are linii !%'
$$ LANGUAGE SQL ;
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 725


Continum cu o funcie care se substituie clauzei ON DELETE CASCADE din
comenzi CREATE/ALTER TABLE. La drept vorbind, aveam nevoie de o asemenea
procedur doar n DB2 i Oracle, ns o vom redacta i n celelalte dou servere BD.
Listing-ul 16.50 conine funcia f_on_delete_cascade_factura dedicat tergerii unei
facturi i tuturor nregistrrilor copil ale acesteia:
Listing 16.50. O funcie-procedur de tip ON DELETE CASCADE
CREATE OR REPLACE FUNCTION f_on_delete_cascade_factura (nrfact_ facturi.nrfact%TYPE)
RETURNS VOID AS $$
DELETE FROM incasfact WHERE nrfact = $1 ;
DELETE FROM liniifact WHERE nrfact = $1 ;
DELETE FROM facturi WHERE nrfact = $1 ;
$$ LANGUAGE SQL ;


Continum cu o procedur dedicat admiterii la master, admitere discutat n
paragraful 13.3.2. Mai nti crem cele dou tabele, MASTERE i CANDIDAI
vezi listing 16.51.
Listing 16.51. Crearea (PostgreSQL) celor dou tabele pentru admiterea la master
CREATE TABLE mastere (
Spec VARCHAR(4) PRIMARY KEY,
DenSpec VARCHAR(40) NOT NULL,
NrLocuri NUMERIC(3),
NrLocuri_Ocupate NUMERIC(3)
) ;

CREATE TABLE candidati (
IdCandidat NUMERIC(6) NOT NULL PRIMARY KEY,
NumePren VARCHAR(50) NOT NULL,
Media_AS NUMERIC(4,2) NOT NULL CONSTRAINT ck_media_as
CHECK (media_as BETWEEN 5 AND 10),
Media_Lic NUMERIC(4,2) NOT NULL CONSTRAINT ck_media_lic
CHECK (media_lic BETWEEN 6 AND 10),
Spec1 VARCHAR(4) NOT NULL REFERENCES mastere(Spec),
Spec2 VARCHAR(4) REFERENCES mastere(Spec),
Spec3 VARCHAR(4) REFERENCES mastere(Spec),
Spec4 VARCHAR(4) REFERENCES mastere(Spec),
Spec5 VARCHAR(4) REFERENCES mastere(Spec),
Spec6 VARCHAR(4) REFERENCES mastere(Spec),
Spec_Repartizat VARCHAR(4) REFERENCES mastere(Spec),
CONSTRAINT ck_spec CHECK (
COALESCE(spec2, ' ') = COALESCE(spec2, spec3, ' ') AND
COALESCE(spec3, ' ') = COALESCE(spec3, spec4, ' ') AND
COALESCE(spec4, ' ') = COALESCE(spec4, spec5, ' ') AND
COALESCE(spec5, ' ') = COALESCE(spec5, spec6, ' ') )
) ;


Urmeaz popularea celor dou tabele vezi listing 16.52.
Listing 16.52. Popularea (PostgreSQL) tabelelor MASTERE i CANDIDAI
726 Capitolul 16
DELETE FROM candidati ;
DELETE FROM mastere ;

INSERT INTO mastere (Spec, DenSpec, NrLocuri) VALUES ('SIA',
'Sisteme informationale pentru afaceri', 4) ;
INSERT INTO mastere (Spec, DenSpec, NrLocuri) VALUES ('FAB', 'Finante-Asigurari-Banci', 6) ;
INSERT INTO mastere (Spec, DenSpec, NrLocuri) VALUES ('CEA',
'Contabilitate, expertiza si audit', 6) ;
INSERT INTO mastere (Spec, DenSpec, NrLocuri) VALUES ('MARK', 'Marketing', 5) ;
INSERT INTO mastere (Spec, DenSpec, NrLocuri) VALUES ('EAI',
'Economie si afaceri internationale', 5) ;
INSERT INTO mastere (Spec, DenSpec, NrLocuri) VALUES ('MRU',
'Managementul resurselor umane', 4) ;

INSERT INTO candidati VALUES ( 1, 'Popescu I. Irina', 7.5, 9.50, 'FAB', 'MARK', 'EAI', 'MRU',
NULL, NULL, NULL);
INSERT INTO candidati VALUES ( 2, 'Babias D. Ecaterina', 8.5, 9.20, 'SIA', 'EAI', NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES ( 3, 'Strat P. Iulian', 7.35, 9.50, 'CEA', 'EAI', 'MRU', 'SIA',
'FAB', 'MARK', NULL);
INSERT INTO candidati VALUES ( 4, 'Georgescu M. Honda', 8.5, 9.00, 'MRU', 'MARK', 'EAI',
'FAB', 'CEA', 'SIA', NULL);
INSERT INTO candidati VALUES ( 5, 'Munteanu A. Optimista', 9.5, 9.50, 'SIA', 'CEA', 'FAB',
NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES ( 6, 'Dumitriu F. Dura', 9.5, 10, 'EAI', NULL, NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES ( 7, 'Mesnita G. Plinutul', 10, 10, 'EAI', 'MARK', 'MRU', NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES ( 8, 'Greavu V. Doru', 9.25, 8.50, 'SIA', 'CEA', 'FAB', NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES ( 9, 'Baboi P. Iustina', 8.5, 8.50, 'SIA', NULL, NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (10, 'Postelnicu I. Irina', 9.5, 8.25, 'MARK', NULL, NULL,
NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES (11, 'Fotache H. Fanel', 9.75, 9.50, 'MRU', NULL, NULL,
NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES (12, 'Moscovici J. Cristina', 9.80, 9.30, 'CEA', 'SIA', 'FAB',
NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES (13, 'Rusu I. Vanda', 7.80, 9.10, 'FAB', 'CEA', 'MARK', 'MRU',
NULL, NULL, NULL);
INSERT INTO candidati VALUES (14, 'Spinu M. Algebra', 7.25, 9.00, 'SIA', 'CEA', 'FAB', NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (15, 'Sandovici I. Irina', 7.05, 7.50, 'MARK', 'MRU', 'EAI', 'CEA',
NULL, NULL, NULL);
INSERT INTO candidati VALUES (16, 'Plai V. Picior', 7.5, 7.90, 'EAI', 'SIA', 'CEA', 'FAB',
NULL, NULL, NULL);
INSERT INTO candidati VALUES (17, 'Ambuscada B. Cristina', 8.25, 9.50, 'SIA', 'CEA',
'FAB', 'EAI', NULL, NULL, NULL);
INSERT INTO candidati VALUES (18, 'Pinda A. Axinia', 8.75, 9.00, 'SIA', 'FAB', 'CEA', NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (19, 'Planton V. Grigore', 9.25, 9.50, 'FAB', NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (20, 'Sergentu I. Zece',7.5, 9.50, 'FAB', 'CEA', NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (21, 'Ababei T. Marian-Vasile', 7.5, 9.50, 'CEA', 'MRU', NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (22, 'Popistasu J. Maria', 7.5, 9.50, 'FAB', 'EAI', 'CEA', 'MRU',
NULL, NULL, NULL);
INSERT INTO candidati VALUES (23, 'Plop R. Robert', 7.5, 9.50, 'FAB', 'MARK', 'MRU', NULL,
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 727
NULL, NULL, NULL);
INSERT INTO candidati VALUES (24, 'Aflorei H. Crina',7.5, 9.50, 'EAI', 'SIA', NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (25, 'Afaunei P. Gina',7.5, 9.50, 'SIA', NULL, NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (26, 'Vatamanu I. Alexandrina', 7.5, 9.50, 'MRU', 'MARK', 'EAI',
NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES (27, 'Lovin P. Marian',7.5, 9.50, 'MARK', 'MRU', NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (28, 'Antiteza W. Florin', 7.5, 9.50, 'EAI', 'MARK', 'MRU',
NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES (29, 'Prepelita V. Ion', 7.5, 9.50, 'EAI', NULL, NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (30, 'Cioara X. Sanda', 7.5, 9.50, 'CEA', 'FAB', 'EAI', NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (31, 'Metafora Y. Vasile',7.5, 9.50, 'SIA', 'CEA', NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (32, 'Strasina R. Elvis', 7.5, 9.50, 'CEA', NULL, NULL, NULL,
NULL, NULL, NULL);
INSERT INTO candidati VALUES (33, 'Durere V. Vasile', 7.5, 9.50, 'FAB', 'CEA', 'EAI', 'MARK',
'MRU', NULL, NULL);
INSERT INTO candidati VALUES (34, 'Sedentaru L. Marius-Daniel',7.5, 9.50, 'MARK', 'MRU',
'EAI', NULL, NULL, NULL, NULL);
INSERT INTO candidati VALUES (35, 'Zgircitu I. Daniel',7.5, 9.50, 'MRU', NULL, NULL, NULL,
NULL, NULL, NULL);


Funcia-procedur p_admitere (listing 16.53) aduce cteva nouti PL/pgSQL.
Cursorul c_mastere este unul parametrizat, la deschiderea sa fcndu-se filtrarea n
funcie de valoarea curent a parametrului CodCl_. Ambele cursoare prezint
opiunea FOR UPDATE ceea ce nseamn c, pe baza nregistrrii curente a
cursorului, se va putea face actualizarea liniei din tabela de baz din care provine
cursorul. Din pcate, n PL/pgSQL lucrurile funcioneaz numai pentru cursorul
c_mastere (nu tiu de ce pentru c_candidai lucrurile nu sunt n regul la folosirea
clauzei FOR UPDATE). Exist, de asemenea, trei variabile de tip RECORD care este
un tip linie (nregistrare) general.
Listing 16.53. Procedura PL/pgSQL pentru admiterea candidailor
CREATE OR REPLACE FUNCTION p_admitere () RETURNS VOID AS
$$
DECLARE
DECLARE c_mastere CURSOR (spec_ mastere.spec%TYPE) IS
SELECT * FROM mastere WHERE spec = spec_ AND NrLocuri > NrLocuri_Ocupate
FOR UPDATE ;
DECLARE c_candidati CURSOR IS
SELECT * FROM candidati ORDER BY Media_AS * 0.6 + Media_Lic * 0.4 DESC
FOR UPDATE ;
rec_candidati RECORD ;
rec_optiuni RECORD ;
rec_mastere RECORD ;

BEGIN
UPDATE mastere SET NrLocuri_Ocupate = 0 ;
UPDATE candidati SET spec_repartizat = NULL ;

728 Capitolul 16
OPEN c_candidati ;
LOOP
FETCH c_candidati INTO rec_candidati ;
IF NOT FOUND THEN
EXIT ;
END IF ;

FOR rec_optiuni IN (
SELECT rec_candidati.IdCandidat, 1 AS OptiuneNr, rec_candidati.Spec1 AS Spec
UNION
SELECT rec_candidati.IdCandidat, 2 AS OptiuneNr, rec_candidati.Spec2 AS Spec
UNION
SELECT rec_candidati.IdCandidat, 3 AS OptiuneNr, rec_candidati.Spec3 AS Spec
UNION
SELECT rec_candidati.IdCandidat, 4 AS OptiuneNr, rec_candidati.Spec4 AS Spec
UNION
SELECT rec_candidati.IdCandidat, 5 AS OptiuneNr, rec_candidati.Spec5 AS Spec
UNION
SELECT rec_candidati.IdCandidat, 6 AS OptiuneNr, rec_candidati.Spec6 AS Spec
ORDER BY 2 ) LOOP

IF rec_optiuni.Spec IS NULL THEN
EXIT ;
END IF ;

OPEN c_mastere (rec_optiuni.Spec) ;
FETCH c_mastere INTO rec_mastere ;
IF FOUND THEN
UPDATE mastere SET NrLocuri_Ocupate = NrLocuri_Ocupate + 1
WHERE CURRENT OF c_mastere ;
UPDATE candidati SET spec_repartizat = rec_optiuni.Spec
WHERE IdCandidat = rec_candidati.IdCandidat ;
-- nu functioneaza CURRENT OF c_candidati !!!!!
CLOSE c_mastere ;
EXIT;
ELSE
CLOSE c_mastere ;
END IF ;
END LOOP ;
END LOOP ;
CLOSE c_candidati ;
END ;
$$ LANGUAGE plpgsql ;


Funcia procedur ncepe prin a face curenie, NULL-iznd atributul
spec_repartizat pentru toi candidaii i zeroriznd valorile atributului
NrLocuri_Ocupate pentru toate specializrile de master. Apoi se parcurg candidaii
n ordinea descresctoare a mediei de admitere (expresia Media_AS * 0.6 +
Media_Lic * 0.4). Pentru fiecare candidat se constuiete un cursor ad-hoc ale crui
nregistrri conin opiunile pe care le-a bifat candidatul (ordinea opiunilor este
esenial). Se parcurg toate aceste opiuni pn cnd, fie candidatului i se gsete o
specializare la care mai sunt locuri neocupate, fie opiunea urmtoare este nul
(ceea ce nseamn c pe candidat nu l-au interesat toate cele ase specializri). Dac
i se gsete o specializare (dorit) n care exist locuri disponibile, atunci pentru
SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 729
acest candidat atributul spec_repartizat preia numele specializrii, iar aceasteia i se
incrementeaz numrul locurilor ocupate.
Lansarea n execuie este simpl:
SELECT p_admitere ()

Dup lansarea procedurii examinm o parte din coninutul tabelei CANDIDAI
ordonat dup medie vezi figura 16.24 - i observm c, pe linia 11, candidata
Ambuscad B. Cristina a avut ca prim opiunea specializarea SIA (Sisteme
informaionale pentru afaceri), ns a fost repartizat la CEA (Contabilitate,
Expertiz i Audit). Motivul este c la SIA au existat doar patru locuri care au fost
ocupate de ali cadidai cu medie mai mare.
n figur dou candidate, Baboi P. Iustina i Afaunei P. Gina sunt respinse
(valoarea atributului spec_repartizat este NULL) ntruct acestea au dorit numai
aceast specializare ale crei locuri au fost ocupate de candidaii cu medii mai
mari.

Figura 16.24. Verificarea funciei-procedur p_admitere

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