Sunteți pe pagina 1din 179

BÂRA ADELA

BOTHA IULIANA
DIACONIŢA VLAD
LUNGU ION
VELICANU ANDA

BAZE DE DATE.
LIMBAJUL PL/SQL

BUCUREŞTI
2009
Baze de date. Limbajul PL/SQL

CUPRINS

INTRODUCERE ............................................................................................... . - 3 -
CAPITOLUL I. INTRODUCERE ÎN PL/SQL (PROCEDURAL LANGUAGE
EXTENSION TO SQL) ....................................................................................... - 4 -
I.1. INTRODUCERE............................................................................................. - 4 -
I.2. BLOCURI PL/SQL......................................................................................... - 5 -
I.3. UTILIZAREA OPERATORILOR ŞI FUNCŢIILOR ÎN PL/SQL................. - 7 -
CAPITOLUL II. VARIABILE PL/SQL ............................................................ - 10 -
II.1. DECLARARE ŞI INIŢIALIZARE ............................................................. - 10 -
II.2. TIPURI DE VARIABILE............................................................................ - 10 -
II.2.1. Variabile Scalare................................................................................... - 10 -
II.2.2. Tipuri de date compuse......................................................................... - 16 -
CAPITOLUL III. INTERACŢIUNEA CU SERVERUL ORACLE PRIN
COMENZI SQL ................................................................................................. - 20 -
CAPITOLUL IV. STRUCTURI FUNDAMENTALE DE PROGRAMARE . - 28 -
IV.1. STRUCTURI ALTERNATIVE................................................................. - 28 -
IV.2. STRUCTURI REPETITIVE ...................................................................... - 32 -
CAPITOLUL V. CURSORI ŞI TABELE INDEX BY...................................... - 37 -
V.1. CURSORUL IMPLICIT.............................................................................. - 37 -
V.2. CURSORUL EXPLICIT ............................................................................. - 39 -
V.3. TIPURI DE TABELE INDEX BY.............................................................. - 55 -
CAPITOLUL VI. TRATAREA EXCEPŢIILOR ............................................. - 58 -
VI.1. TRATAREA EXCEPŢIILOR PREDEFINITE ......................................... - 59 -
VI.2. TRATAREA EXCEPŢIILOR NON-PREDEFINITE ............................... - 63 -
VI.3. TRATAREA EXCEPŢIILOR DEFINITE DE UTILIZATOR.................. - 66 -
CAPITOLUL VII. SUBPROGRAME PL/SQL................................................. - 71 -
VII.1. PROCEDURI............................................................................................ - 71 -
VII.2. FUNCŢII................................................................................................... - 82 -
CAPITOLUL VIII. PACHETE DE SUBPROGRAME ................................... - 90 -
CAPITOLUL IX. DECLANŞATORI................................................................ - 106 -
IX.1. DECLANŞATORI PENTRU OPERAŢIILE DE MANIPULARE A
DATELOR ................................................................................................ - 106 -
IX.2. DECLANŞATORI PENTRU OPERAŢII DE DEFINIRE A DATELOR ŞI
PENTRU OPERAŢII LA NIVER DE SERVER ...................................... - 117 -
CAPITOLUL X. REALIZAREA DE VIDEOFORMATE ŞI RAPOARTE - 120 -
X.1. VIDEOFORMATE.................................................................................... - 120 -
X.2. RAPOARTE .............................................................................................. - 132 -
X.3. INTEGRARE FORMS-REPORTS ........................................................... - 137 -
CAPITOLUL XI. EXERCIŢII RECAPITULATIVE ..................................... - 145 -
CERINŢE PROPUSE........................................................................................ - 145 -
TESTE GRILĂ PROPUSE ............................................................................... - 147 -
SUBIECTE DATE LA EXAMENUL DE ADMITERE LA PROGRAMUL DE
MASTERAT DE APROFUNDARE INFORMATICĂ ECONOMICĂ Sesiunea
Septembrie 2008........................................................................................ - 164 -
BIBLIOGRAFIE ................................................................................................. - 169 -
ANEXE ................................................................................................................. - 170 -
ANEXA 1 – Scriptul pentru crearea tabelelor bazei de date............................. - 170 -
ANEXA 2 – Schema conceptuală a bazei de date utilizată în exemple ............ - 176 -
ANEXA 3 – Lista excepţiilor predefinite Oracle .............................................. - 177 -

-2 -
Baze de date. Limbajul PL/SQL

INTRODUCERE

De mai puţin de un deceniu, Oracle este un nume des întâlnit în majoritatea


companiilor medii sau mari, fie că este vorba de sistemul de gestiune a bazelor de date sau de
aplicaţiile integrate de întreprindere.
Lucrarea de faţă se doreşte a fi un instrument deosebit de util în special studenţilor de
anul II ai Facultăţii de Cibernetică, Statistică şi Informatică Economică, dar şi oricui doreşte
să înveţe sau să aprofundeze limbajul procedural specific Oracle - PL/SQL.
Capitolele I-IX grupează principalele facilităţi ale limbajului PL/SQL, punând
accentul îndeosebi pe exemplificări practice. Acestea sunt testate în mediul de programare
Oracle SQL Developer, disponibil gratuit pe site-ul: http://otn.oracle.com.
Capitolul X face trecerea spre programarea interfeţelor de interacţiune cu utilizatorul
folosind produsele Oracle Forms şi Oracle Reports. Capitolul prezintă exemple succinte de
creare a videoformatelor şi a rapoartelor, utilizând atât asistenţi de tip Wizard, cât şi comenzi
PL/SQL.
Capitolul XI cuprinde exerciţii recapitulative, constând în special din teste grilă, care
acoperă toate subiectele abordate. În plus, se pot găsi aici şi subiectele date în anul 2008 la
examenul de admitere la masterat.
În anexe cititorii vor găsi codul sursă pentru crearea tabelelor utilizate în exemple. În
plus, este inclusă aici şi lista excepţiilor predefinite ale serverului Oracle.
Autorii mulţumesc colegilor din catedra de Informatică Economică pentru sugestiile şi
sfaturile deosebit de utile acordate în vederea elaborării lucrării de faţă.

Autorii

-3 -
Baze de date. Limbajul PL/SQL

CAPITOLUL I.
INTRODUCERE ÎN PL/SQL
(PROCEDURAL LANGUAGE EXTENSION TO SQL)

I.1. INTRODUCERE
PL/SQL este un limbaj procedural structurat pe bloc, programele putând fi împărţite în
blocuri logice, construcţiile PL/SQL conţinând structuri de control procedurale şi comenzi
descriptive SQL. Blocurile PL/SQL sunt procesate de motorul PL/SQL care poate fi rezident
pe serverul ORACLE sau pe un alt instrument de dezvoltare (ex.: Oracle Forms, Reports,
JDeveloper etc.).
Programarea în PL/SQL este modularizată, în acest sens se utilizează blocurile care
grupează instrucţiunile. Tipurile de date din SQL pot fi folosite în PL/SQL.
Pentru a putea lucra cu limbajul PL/SQL avem nevoie în primul rând de o conexiune
la o bază de date Oracle şi de un instrument de dezvoltare prin care să interacţionăm cu
serverul Oracle. Pentru instalarea şi configurarea instanţei bazei de da date se poate utiliza
ghidul electronic pus la dispoziţie de către compania Oracle odată cu kit-ul de instalare. Ca
instrument de dezvoltare se poate utiliza SQL*plus care se instalează împreună cu instanţa
Oracle sau se poate utiliza SQL Developer, un mediu de dezvoltare mai prietenos disponibil
gratuit pe site-ul companiei Oracle (www.oracle.com).
Schema bazei de date pe care se bazează exemplele din această carte este următoarea:

Figura 1 – Schema bazei de date utilizată în exemple


Scriptul pentru crearea acestor tabele este redat în Anexa 1 şi se poate descărca de pe
site-ul http://bd.ase.ro împreună cu scriptul pentru adăugarea înregistrărilor.

-4 -
Baze de date. Limbajul PL/SQL

I.2. BLOCURI PL/SQL


Orice unitate PL/SQL conţine unul sau mai multe blocuri, complet separate sau
imbricate. Fiecare bloc este compus din secţiuni care pot fi obligatorii sau opţionale.
Componentele unui bloc PL/SQL
Un bloc PL/SQL este compus din până la 3 secţiuni: declarativă (opţională),
executabilă (obligatorie) şi de tratare a excepţiilor (opţională). Structura unui bloc PL/SQL
poate fi prezentată astfel:
DECLARE (Opţional)
variabile, cursori, excepţii
BEGIN (Obligatoriu)
comenzi SQL (asigură accesul la baza de date)
structuri de programare procedurală PL/SQL
EXCEPTION (Opţional)
acţiuni ce se execută când apare o eroare
END; (Obligatoriu)
În cadrul blocului pot apărea instrucţiuni SQL care asigură accesul la baza de date, de
exemplu pentru efectuarea unor actualizări, dar operaţiile efectuate cu variabilele PL/SQL în
cadrul instrucţiunilor procedurale nu presupun accesarea bazei de date.
Observaţii:
 se foloseşte (;) după fiecare instrucţiune SQL sau instrucţiune de control PL/SQL;
 blocul PL/SQL se termină cu (;);
 comentariile se pot realiza folosind (--) pentru fiecare linie de cod comentată sau (/*
*/) pentru mai multe linii comentate;
 se foloseşte (/) pentru a lansa un bloc anonim în bufferul SQL;
 instrucţiunile nu sunt CASE SENSITIVE.
Blocurile PL/SQL pot fi executate o singură dată (cazul blocurilor anonime), pot fi
stocate în baza de date pentru a fi apelate ulterior (cazul funcţiilor, procedurilor şi pachetelor
stocate) sau pot fi realizate şi apelate la nivelul programelor de aplicaţii (funcţii, proceduri,
triggeri de aplicaţii). În paragrafele următoare ne vom referi pe scurt la fiecare dintre aceste
tipuri de blocuri, urmând să le detaliem în capitole separate.
Blocurile anonime prezintă următoarele caracteristici:
 nu sunt stocate în baza de date;
 se declară inline, în locul în care se doreşte execuţia lor;
 se execută în momentul rulării;
Dacă doriţi să reutilizaţi un bloc anonim vă sfătuim să salvaţi codul sursă pe hard disk
într-un fişier de tip .sql sau .txt.

La modul general un bloc PL/SQL anonim poate avea următoarea formă:


DECLARE
-- declararea variabilelor se face precizând tipul de date şi realizându-se eventualele
iniţializări;
-- de exemplu: v_variabila tip_de_date;

-5 -
Baze de date. Limbajul PL/SQL

BEGIN
-- instrucţiuni executabile;
EXCEPTION
WHEN excepţie THEN acţiune;
END;
/

Blocurile PL/SQL se pot imbrica şi se pot eticheta cu <<eticheta_bloc>> pentru a


putea fi ulterior accesate variabilele, adresare lor fiind realizată astfel: eticheta_bloc.variabila.
DECLARE
---------
BEGIN
……….
<< eticheta_bloc >>
DECLARE
…………..
BEGIN
…………..
END eticheta_bloc;
END;
/

Proceduri şi funcţii
Sunt blocuri PL/SQL care au nume şi pot fi stocate la nivelul bazei de date sau la nivel
de aplicaţie (de exemplu în mediul de dezvoltare Oracle Developer Suite – Forms şi Reports).
În cazul procedurilor şi funcţiilor secţiunea DECLARE este înlocuită de definiţia
acesteia prin sintaxa CREATE [or replace] PROCEDURE/FUNCTION (lista parametrilor şi
tipul acestora), de exemplu:
CREATE [OR REPLACE] PROCEDURE nume_procedura (lista parametri)
IS
……….
BEGIN
……….
[EXCEPTION]
……….
END;
/

CREATE [OR REPLACE] FUNCTION nume_functie (lista parametri)


RETURN tip_data
IS
BEGIN
………

-6 -
Baze de date. Limbajul PL/SQL

RETURN valoare
[EXCEPTION]
………
END;
/

Pachetele de subprograme sunt utilizate pentru a grupa mai multe proceduri şi funcţii
utilizate pentru un anumit tip de prelucrări.
Declanşatorii pe baza de date sunt blocuri PL/SQL asociate tabelelor (de bază sau
virtuale) şi lansate automat în execuţie când are loc o comanda de manipulare
(insert/update/delete).
Declanşatorii de aplicaţie sunt blocuri PL/SQL asociate unor evenimente din cadrul
aplicaţiei (de exemplu: deplasarea mouse-ului, apăsarea unui buton) şi lansate în execuţie
automat la apariţia acestor evenimente.

I.3. UTILIZAREA OPERATORILOR ŞI FUNCŢIILOR ÎN PL/SQL


Operatorii utilizaţi
In cadrul blocurilor PL/SQL se pot utiliza aceiaşi operatori ca şi în cazul limbajului
SQL. In plus faţă de aceştia apare operatorul de atribuire (:=).
Operator Caracteristici
+, -, *, /, ** (op. exponenţial) Operatori aritmetici
AND, OR, NOT Operatori logici
<, >, =, >=, <=, <>, != Operatori de comparaţie
BETWEEN ... AND ... Operator de verificare a apartenenţei la un interval de valori
IN(listă) Operator de verificare a apartenenţei la o listă de valori
LIKE Operator de comparare cu un şablon
% - oricâte caractere; _ - un caracter;
IS NULL Operator care verifică dacă o variabilă are valoarea NULL
|| Operator de concatenare
@ Operator de conectare la distanţă
& Operatori pentru adresarea variabilelor de substituţie
sau &&
:= Operator de atribuire

Funcţii SQL suportate în PL/SQL


În cadrul instrucţiunilor descriptive sunt suportate toate tipurile de funcţii SQL
(inclusiv funcţiile de grup în cadrul instrucţiunii SELECT).
Instrucţiunile PL/SQL suportă funcţii la nivel de înregistrare (single-row): numerice,
caracter, data, de conversie etc., dar NU suportă funcţii de grup (SUM, MIN, MAX, AVG,
COUNT, STDDEV) sau funcţia DECODE decât în cadrul instrucţiunilor SQL din bloc. De
exemplu, nu se pot utiliza într-un bloc PL/SQL construcţii de forma:
IF DECODE(...) THEN ... sau IF AVG(...) THEN ... .
Limbajul PL/SQL converteşte tipurile de date dinamic (de exemplu: o valoare
numerică la o variabilă char), în acest sens având loc conversii implicite: caracter <-> numeric

-7 -
Baze de date. Limbajul PL/SQL

şi caracter <->data. Pentru conversii explicite se utilizează funcţiile TO_DATE,


TO_NUMBER, TO_CHAR în mod asemănător cu limbajul SQL.
Afişarea pe ecran a valorilor variabilelor sau a mesajelor în PL/SQL se realizează prin
apelarea funcţiilor PUT_LINE sau PUT din pachetul standard DBMS_OUTPUT. Aceste două
funcţii primesc ca parametru un şir de caractere şi-l afişează. Diferenţa dintre cele două funcţii
este aceea că prima afişează mesajul după care trece la linia următoare, iar cea de a doua
afişează mesajul pe linia curentă. Funcţiile PUT_LINE şi PUT sunt necesare deoarece nu
putem accesa variabilele declarate în blocurile PL/SQL direct din mediul SQL (pentru a le
afişa cu PROMPT).
În unele medii de dezvoltare, ca de exemplu în SQL*Plus, trebuie utilizată la începutul
sesiunii comanda SET SERVEROUTPUT ON care activează bufferul pentru afişare.
SET SERVEROUTPUT ON
........................
DECLARE
………………….
BEGIN
…………………..
DBMS_OUTPUT.PUT_LINE ('. ......................... ');
DBMS_OUTPUT.PUT ('. ......................... ');
END;
/
De exemplu, vom realiza un bloc PL/SQL care să afişeze un mesaj. Pentru a executa
blocul utilizăm mediul de dezvoltare SQL Developer. Mai întâi este necesară stabilirea
conexiunii cu serverul Oracle: click dreapta pe eticheta Connections din stânga ecranului şi
completăm detaliile referitoare la conexiune: nume utilizator şi parola, adresa IP a serverului
şi portul, numele instanţei bazei de date.

Figura 2 – Realizarea conexiunii cu serverul Oracle

-8 -
Baze de date. Limbajul PL/SQL

După conectare apare fereastra în care putem scrie şi executa blocurile, în mod
asemănător cu modul de lucru cu instrucţiunile SQL. Pentru execuţia blocurilor se dă click pe
unul dintre butoanele Execute Statement (F9) sau Run Script (F5).

Figura 3 – Execuţia blocurilor PL/SQL în SQL Developer

Codul sursă al blocului este redat mai jos:


SET SERVEROUTPUT ON
--INTRODUCEM COMENTARII: in acest caz secţiunea DECLARE
lipseşte
--blocul afişează un mesaj
BEGIN
DBMS_OUTPUT.PUT_LINE ('Limbajul PL/SQL este un limbaj
procedural!');
DBMS_OUTPUT.PUT_LINE('Un bloc PL/SQL contine trei
seciuni: DECLARATIVA, EXECUTABILA, DE TRATARE A
EXCEPTIILOR');
END;
/

EXERCIŢII PROPUSE

1. Recapitulaţi noţiunile de bază ale limbajului SQL, precum şi comenzile utilizate.

2. Creaţi o conexiune nouă în SQL Developer şi testaţi programul clasic Hello World!

-9 -
Baze de date. Limbajul PL/SQL

CAPITOLUL II.
VARIABILE PL/SQL

II.1. DECLARARE ŞI INIŢIALIZARE


Declararea variabilelor se realizează în zona declarativă delimitată prin DECLARE a
blocului sau sub-blocului. Iniţializarea variabilelor se poate face la declarare sau în zona de
execuţie între BEGIN şi END.
Variabilele vor fi vizibile şi în blocurile imbricate, incluse în el, mai puţin în sub-
blocurile în care numele lor este redefinit, la fel ca în cazul limbajelor de programare
structurate, unde semnificaţia unui nume definit de utilizator într-un bloc/sub-bloc este dată
de cea mai apropiată declaraţie anterioară locului folosirii.
La declararea variabilelor PL/SQL se precizează obligatoriu tipul de dată şi opţional
anumite restricţii şi un şir valid de valori. Declararea şi iniţializarea se realizează astfel:
nume_variabila [CONSTANT] TIP_DATA [NOT NULL] [:= | DEFAULT expresie];

Constantele în PL/SQL trebuie obligatoriu iniţializate, iar ulterior nu îşi vor putea
schimba valoarea. Variabilele pentru care se precizează restricţia NOT NULL trebuie
obligatoriu iniţializate, iar ulterior nu vor putea primi valoarea NULL.

Pentru a putea urmări mai uşor codul sursă se foloseşte următoarea convenţie de
notare:
c_nume Constanta - pentru declararea constantelor;
v_nume Variabila - pentru declararea variabilelor;
g_nume Variabila_globala – pentru variabilele globale definite în zona de
specificaţii a unui pachet de subprograme şi valabile pentru toate procedurile şi
funcţiile pachetului.

II.2. TIPURI DE VARIABILE


Variabile PL/SQL
 Scalare
 Compozite
 Referinţă
 LOB (Large Objects): NCLOB, CLOB, BLOB, BFILE
 Obiect
Variabile non-PL/SQL: variabile de mediu (BIND VARIABLES)

II.2.1. Variabile Scalare


Tipurile scalare conţin valori simple, o variabila scalară poate conţine la un moment
dat o singură valoare şi corespunde în principal tipurilor de date Oracle. Cele mai cunoscute şi
utilizate tipuri de date sunt prezentate în tabelul următor:
 char (lung_max) - lungime fixă de max 32.767 bytes
 varchar2 (lung_max) – lungime variabilă de max 32.767 bytes

- 10 -
Baze de date. Limbajul PL/SQL

 long [şir de caractere de lungime variabilă 2GB]


 number (precizie,scală)
 boolean (true, false, null)
 date
 binary_integer şi pls_integer (numere întregi între -2147483647 şi
2147483647)
 binary_float şi binary_double (pentru numere reale în varianta
Oracle 10g)
 timestamp (pentru fracţiuni de secundă)

Pentru alte tipuri de date puteţi consulta capitolul 3 din [ORAPL].

Exemple:
v_functie varchar2(9);
v_numar binary_integer:=0;
v_totalsal number(9,2):=0;
v_datainceput date:=sysdate+7;
c_taxa constant number(3,2):=8.25;
v_valid boolean not null:=true;

Afişarea variabilelor PL/SQL se realizează prin intermediul funcţiei PUT_LINE din


pachetului DBMS_OUTPUT. Se poate utiliza operatorul de concatenare ( || ) pentru a afişa
mai multe mesaje sau variabile pe aceeaşi linie, de exemplu:
DBMS_OUTPUT.PUT_LINE ('VALOAREA VARIABILEI ESTE:' ||variabila);

Popularea variabilelor cu valori din tabelele bazei de date


Se utilizează comanda SELECT cu clauza INTO pentru popularea variabilelor PL/SQL cu
valori ale atributelor din tabele, însă pentru aceasta cererile SELECT din cadrul blocurilor
PL/SQL trebuie să furnizeze o singură linie rezultat, în caz contrar se va semnala o eroare.
Pentru a putea accesa o anumită înregistrare sau valoare a unei coloane dintr-o tabelă este
obligatoriu să utilizăm variabile PL/SQL, aşa cum se observă şi în exemplul următor:

Exemplu: Să se afişeze numele angajatului cu codul 100:


SET SERVEROUTPUT ON
DECLARE
--declaram variabila v_nume în care vom returna numele
angajatului:
v_nume VARCHAR2(20);
BEGIN
-- utilizam instructiunea SELECT cu clauza INTO pentru
a popula variabila:
SELECT nume INTO v_nume
FROM angajati WHERE id_angajat = 100;
-- afisam valoarea varibilei:
DBMS_OUTPUT.PUT_LINE('NUMELE ANGAJATULUI ESTE:' ||
v_nume);
END;
/

Rezultatele execuţiei blocului sunt prezentate în figura următoare:

- 11 -
Baze de date. Limbajul PL/SQL

Figura 4 –Execuţia blocului PL/SQL

1. Atributul %TYPE
Pentru a declara o variabilă PL/SQL care să aibă acelaşi tip de date cu o anumită
coloană dintr-o tabelă sau cu o altă variabilă declarată anterior putem folosi atributul %TYPE.
Declararea unei variabile cu %TYPE se face astfel:
variabila tabelă.nume_coloană%TYPE;
sau
variabila1 tip_dată;
variabila2 variabila1%TYPE;

Exemple:
Să se afişeze numele şi prenumele angajatului cu codul 100:
DECLARE
--declaram doua variabile pentru nume şi prenume:
v_nume angajati.nume%TYPE;
v_prenume angajati.prenume%TYPE;
BEGIN
-- populam şi afisam variabilele:
SELECT nume, prenume
INTO v_nume, v_prenume
FROM angajati
WHERE id_angajat = 100;
DBMS_OUTPUT.PUT_LINE('NUMELE ANGAJATULUI ESTE:' ||
v_nume||' '||v_prenume);
END;
/

- 12 -
Baze de date. Limbajul PL/SQL

Observaţie: Restricţia NOT NULL a unei coloane nu se aplică şi variabilei declarate


prin folosirea atributului %TYPE.

2. Variabile de mediu sau variabile de legătură ale aplicaţiilor gazdă (BIND


VARIABLES)
Acestea sunt variabile de legătură cu aplicaţia în care rulează motorul PL/SQL şi
trebuie declarate în aplicaţie (în mediul gazdă), putând fi accesate şi modificate în cadrul
blocurilor PL/SQL.
După terminarea execuţiei blocului PL/SQL, variabila rămâne în mediul gazdă cu
valoarea primită în urma rulării blocului şi poate fi pasată altui bloc, realizând astfel
transmiterea de valori între blocurile PL/SQL. Variabilele de mediu nu pot fi utilizate în
cadrul procedurilor, funcţiilor sau pachetelor.
Variabilele de mediu se declară în afara blocului PL/SQL cu ajutorul cuvântului cheie
VARIABLE:
VARIABLE g_numevariabilă TIP
Observaţie: pentru declararea unei variabile de mediu de tip NUMBER nu se
specifică precizia şi scala;
Variabilele de mediu se utilizează în cadrul unui bloc PL/SQL sau într-o instrucţiune
SQL din afara blocului prefixate cu (:) astfel:
:host_variabila:=v_variabila;
Variabilele de mediu se afişează în afara blocului cu ajutorul comenzii PRINT (la
afişare variabila nu se va prefixa cu “:”):
PRINT g_numevariabilă

Exemple:
1. Să se afişeze mesajul: Bloc PL/SQL cu ajutorul unei variabile de mediu:
--declaram variabila de mediu:
VARIABLE g_mesaj varchar2(30)
BEGIN
-- atribuim mesajul variabilei
:g_mesaj:='Bloc PL/SQL';
END;
/
-- afisam valoarea variabilei în afara blocului:
PRINT g_mesaj

2. Să se afişeze numărul de comenzi a căror modalitate de completare este online:


SET SERVEROUTPUT ON
-- declaram variabila de mediu:
VARIABLE g_comenzi varchar2(30)
BEGIN
-- utilizam o cerere SQL pentru a calcula si popula
variabila cu numarul de comenzi:
select count(*) into :g_comenzi
from comenzi
where modalitate = 'online';
END;
/
-- afisam variabila:

- 13 -
Baze de date. Limbajul PL/SQL

PRINT g_comenzi

Observaţie: Se poate auto-afişa variabila prin setarea AUTOPRINT ON

Exemplu: Să se selecteze produsele şi preţul acestora pentru acele produse care au


preţul < preţul mediu al produsului cu codul 3133 fără a utiliza o cerere imbricată:
SET SERVEROUTPUT ON
SET AUTOPRINT ON
VARIABLE g_pret number
BEGIN
select avg(pret) into :g_pret
from rand_comenzi
where id_produs = 3133;
END;
/
--utilizam variabila de mediu într-o interogare SQL:
select * from rand_comenzi where pret< :g_pret;

Figura 5 –Execuţia blocului PL/SQL

3. Variabile de substituţie
De regulă, variabilele de substituţie sunt folosite pentru a transmite valori dinspre
mediul SQL*Plus spre comenzile SQL sau blocurile PL/SQL, în timp ce variabilele de
legătură (bind variables) sunt folosite pentru a transmite valori în sens invers sau pentru a
transfera valori între blocuri PL/SQL lansate succesiv (primul bloc setează variabila,
următorul o consultă).
Prin variabilele de substituţie se pot transmite valori comenzilor SQL sau blocurilor
PL/SQL lansate (folosind "&" sau "&&"). Se pot utiliza în comenzile SQL sau în blocurile
PL/SQL prin "&nume_variabila" sau "&&nume_variabila".

- 14 -
Baze de date. Limbajul PL/SQL

Variabilele de substituţie sunt locale sesiunii SQL în care au fost declarate. În mediul
SQL variabilele de substituţie pot fi uşor utilizate prin introducerea de valori de la tastatură
utilizând ACCEPT, se pot defini cu DEFINE sau afişa pe ecran cu PROMPT;

Exemplu: Să se afişeze numărul de comenzi ale angajatului al cărui cod este introdus de la
tastatură de către utilizator prin intermediul variabilei de substituţie &id_angajat:
DECLARE
v_nr_comenzi number(2);
BEGIN
--introducem id-ul angajatului cu ajutorul variabilei
&id_angajat
select count(nr_comanda) into v_nr_comenzi from comenzi
where id_angajat=&id_angajat;
dbms_output.put_line('Angajatul are: '||
v_nr_comenzi||' comenzi');
END;
/

Figura 6 –Execuţia blocului PL/SQL

Într-un bloc PL/SQL se pot utiliza toate tipurile de variabile, respectând însă
caracteristicile şi regulile de utilizare ale acestora. În exemplul următor se utilizază atât
variabila de substituţie s_nume definită şi iniţializată prin comanda DEFINE, cât şi variabila
de legătură g_salariul, dar şi variabila locală v_prenume de acelaşi tip cu coloana nume din
tabela Angajati. Variabila de substituţie definită cu DEFINE va fi implicit de tipul CHAR.

Exemplu: Să se afişeze salariul şi prenumele angajatului cu numele Abel:


SET SERVEROUTPUT ON
-- definim variabila de mediu
VARIABLE g_salariul number
-- definim variabila de substitutie
DEFINE s_nume=Abel
- 15 -
Baze de date. Limbajul PL/SQL

DECLARE
--declaram variabila locala PL/SQL
v_prenume angajati.nume%type;
BEGIN
select prenume,salariul into v_prenume, :g_salariul
from angajati where nume='&s_nume';
DBMS_OUTPUT.PUT_LINE ('Prenumele angajatului este:
'||v_prenume);
END;
/
print g_salariul

II.2.2. Tipuri de date compuse

1. Tipul RECORD
Reprezintă un grup de date logic corelate (de exemplu, datele despre un client: codc,
nume, adresa sunt diferite ca tip dar corelate logic). Atunci când se declară un PL/SQL record
pentru aceste câmpuri, ele pot fi manipulate ca o unitate, fiecare câmp (element al structurii)
având un nume şi un tip de dată. Atributele unui record sunt referite astfel :
nume_record.nume_câmp.
Declarea unui tip record se face astfel:
TYPE nume_record IS RECORD (nume_câmp TIP_DATA [,nume_câmp
TIP_DATA:=|DEFAULT valoare]...);
Iar declararea unei variabile de acest tip:
Variabilă NUME_RECORD;

Exemplu: Utilizând un tip de dată record definit de utilizator să se afişeze preţul


minim al produsului cu codul 3133.
DECLARE
TYPE tip_produse IS RECORD
(v_cod produse.id_produs%type NOT NULL:=3000,
v_den produse.denumire_produs%type,
v_pret_min produse.pret_min%type);
vrec_prod tip_produse;
BEGIN
SELECT id_produs, denumire_produs, pret_min into
vrec_prod from produse where id_produs=3133;
dbms_output.put_line('Produsul: '|| vrec_prod.v_den|| '
are pretul minim: '||vrec_prod.v_pret_min);
END;
/

- 16 -
Baze de date. Limbajul PL/SQL

Figura 7 –Execuţia blocului PL/SQL

Pentru a defini un record pe baza coloanelor unei tabele se foloseşte %rowtype . In


acest caz numele elementelor din record au acelaşi nume ca şi coloanele tabelei, acelaşi tip de
date şi se găsesc în aceeaşi ordine.
NUME_RECORD tabela%ROWTYPE;

Exemplu: Să se rescrie exemplul de mai sus utilizând atributul %ROWTYPE:.


DECLARE
vrec_prod produse%rowtype;
BEGIN
SELECT * into vrec_prod from produse where
id_produs=3133;
dbms_output.put_line('Produsul: '||
vrec_prod.denumire_produs|| ' are pretul minim:
'||vrec_prod.pret_min);
END;
/

EXERCIŢII PROPUSE

1. Specificaţi ce se va afişa la rularea următorului bloc PL/SQL:


DECLARE
v_var1 NUMBER :=100;
v_var2 NUMBER;
v_var3 NUMBER := v_var2;

- 17 -
Baze de date. Limbajul PL/SQL

v_var4 VARCHAR(20) := 'variabila PL/SQL';


v_var5 NUMBER NOT NULL := v_var1;
c_const1 CONSTANT DATE :=
TO_DATE('12/02/2007','dd/mm/yyyy');
c_const2 CONSTANT NUMBER NOT NULL := 2;
c_const3 CONSTANT NUMBER := NULL;
v_var6 NUMBER DEFAULT NULL;

BEGIN
DBMS_OUTPUT.PUT_LINE('variabila 1 = '||v_var1);
DBMS_OUTPUT.PUT_LINE('variabila 2 = '||v_var2);
DBMS_OUTPUT.PUT_LINE('variabila 3 = '||v_var3);
DBMS_OUTPUT.PUT_LINE('variabila 4 = '||v_var4);
DBMS_OUTPUT.PUT_LINE('variabila 5 = '||v_var5);
DBMS_OUTPUT.PUT_LINE('constanta 1 = '||c_const1);
DBMS_OUTPUT.PUT_LINE('constanta 2 = '||c_const2);
DBMS_OUTPUT.PUT_LINE('constanta 3 = '||c_const3);
DBMS_OUTPUT.PUT_LINE('variabila 6 = '||v_var6);
END;
/

2. Specificaţi ce se va afişa la rularea următorului bloc PL/SQL (care conţine blocuri


imbricate, ilustrând domeniul de vizibilitate al unor variabile care au acelaşi nume):
DECLARE
var NUMBER;
BEGIN
var := 1;
DBMS_OUTPUT.PUT_LINE(var);

<<bloc1>>
DECLARE
var NUMBER;
BEGIN
var :=2;
DBMS_OUTPUT.PUT_LINE(var);
END bloc1;

DBMS_OUTPUT.PUT_LINE(var);

<<bloc2>>
DECLARE
var NUMBER;
BEGIN
var :=3;
DBMS_OUTPUT.PUT_LINE(var);

<<bloc3>>
DECLARE
var NUMBER;
BEGIN
var :=4;
DBMS_OUTPUT.PUT_LINE(var);

- 18 -
Baze de date. Limbajul PL/SQL

DBMS_OUTPUT.PUT_LINE(bloc2.var);
END bloc3;
DBMS_OUTPUT.PUT_LINE(var);
END bloc2;
DBMS_OUTPUT.PUT_LINE(var);
END;
/

3. Specificaţi ce se va afişa la rularea următorului bloc PL/SQL:


DECLARE
stoc NUMBER(3):=600;
mesaj VARCHAR2(50):='Produsul 101';
BEGIN
DECLARE
stoc NUMBER(3):=10;
mesaj VARCHAR2(50):='Produsul 102';
um VARCHAR2(10):= ' bucati ';
BEGIN
stoc:= stoc+1;
mesaj:='Stocul pentru '||mesaj||' este de:
'||stoc||um;
DBMS_OUTPUT.PUT_LINE(mesaj);
END;
stoc:= stoc+100;
mesaj:='Stocul pentru '||mesaj||' este de: '||stoc||um;
DBMS_OUTPUT.PUT_LINE(mesaj);
END;
/

4. Să se calculeze suma a două numere, iar rezultatul să se dividă cu 3. Numerele se vor


introduce de la tastatură:

5. Să se afişeze salariul mărit cu un procent. Salariul şi procentul se introduc de la tastatură:

- 19 -
Baze de date. Limbajul PL/SQL

CAPITOLUL III.
INTERACŢIUNEA CU SERVERUL ORACLE
PRIN COMENZI SQL
Interacţiunea se realizează prin intermediul comenzilor de definire a datelor - DDL
(Data Definition Language), limbajul de gestiune a utilizatorilor - DCL (Data Control
Language), limbajul de manipulare a datelor - DML (Data Manipulation Language), limbajul
de control al tranzacţiilor - TPL (Transaction Processing Language) astfel:
 PL/SQL suportă toate comenzile din limbajul de manipulare a datelor (DML) şi din
cel de control al tranzacţiilor (TPL). Un bloc PL/SQL nu e o tranzacţie, comenzile
Commit/ Rollback/ Savepoint sunt independente de bloc dar pot să apară în
interiorul său.
Comenzi DML/TPL Executie
SELECT, INSERT, UPDATE, Se execută normal in cadrul blocului
DELETE, MERGE

COMMIT, ROLLBACK, Pot apărea în bloc dar au efect asupra


SAVEPOINT tuturor tranzacţiilor din interiorul şi din
afara acestuia.

 PL/SQL nu suportă comenzi de definire a datelor sau de gestiune a utilizatorilor


(DDL sau DCL) în cadrul unui bloc. Pentru executarea acestor comenzi se utilizează
comanda EXECUTE IMMEDIATE ‘comanda DDL’:
Comenzi DDL/DCL Executie
CREATE, ALTER, DROP EXECUTE IMMEDIATE 'CREATE
GRANT, REVOKE TABLE.....'

Exemple:
1. Să se creeze tabela PROD în cadrul unui bloc PL/SQL:
SET SERVEROUTPUT ON
BEGIN
EXECUTE IMMEDIATE 'CREATE table prod AS SELECT * FROM
produse where 1=2';
END;
/

- 20 -
Baze de date. Limbajul PL/SQL

Figura 8 –Execuţia blocului PL/SQL

2. Să se adauge în tabela PROD înregistrări din tabela PRODUSE:


DECLARE
v_codp produse.id_produs%type;
v_denp produse.denumire_produs%type;
v_cat produse.categorie%type;
v_des produse.descriere%type;
BEGIN
--populam variabilele:
SELECT id_produs, denumire_produs, categorie, descriere
INTO v_codp, v_denp, v_cat, v_des
FROM produse where id_produs=3133;
--adaugam valorile variabilelor in tabela:
INSERT INTO prod (id_produs, denumire_produs,
categorie, descriere) VALUES (v_codp, v_denp, v_cat,
v_des);
--afisam valoarea variabilelor:
DBMS_OUTPUT.PUT_LINE ('S-a adaugat in tabela prod
produsul: '||v_codp||' '||v_denp||' '||v_cat);
end;
/
select * from prod;

- 21 -
Baze de date. Limbajul PL/SQL

Figura 9 –Execuţia blocului PL/SQL

Pentru o mai mare flexibilitate se poate declara o variabilă de tip şir de caractere care
să primească comanda de definire a datelor care va fi executată prin Execute Immediate:

Exemple:
1. Să se creeze tabela EMP_SAL prin intermediul unei variabile de tip VARCHAR2.
La creare, în tabela EMP_SAL se va adăuga o nouă înregistrare.
SET SERVEROUTPUT ON
VARIABLE G_EID NUMBER
DECLARE
V_SIR VARCHAR2(200);
BEGIN
:G_EID:=110;
--atribuim şirul de caractere variabilei
V_SIR:='CREATE table emp_sal AS SELECT id_angajat, nume,
prenume, salariul
FROM angajati where id_angajat='||:G_EID;
DBMS_OUTPUT.PUT_LINE (V_SIR);
--utilizam comanda cu valoarea variabilei v_sir:
EXECUTE IMMEDIATE V_SIR;
END;
/
select * from emp_sal;

2. Să se adauge o nouă coloană - STOC în tabela PRODUSE:

- 22 -
Baze de date. Limbajul PL/SQL

DECLARE
V_SIR VARCHAR2(200);
BEGIN
V_SIR:='ALTER TABLE PRODUSE ADD (STOC NUMBER (7))';
DBMS_OUTPUT.PUT_LINE (V_SIR);
EXECUTE IMMEDIATE V_SIR;
END;
/
select * from PRODUSE;

Figura 10 –Execuţia blocului PL/SQL

Manipularea datelor in PL/SQL se face prin limbajul de manipulare a datelor


(comenzile INSERT, UPDATE, DELETE) care pot fi lansate fără restricţii în PL/SQL.

Exemple:
Comanda INSERT:
1. Să se adauge o nouă înregistrare în tabela EMP_SAL:
BEGIN
INSERT INTO EMP_SAL (id_angajat, nume, prenume,
salariul)
VALUES (200, 'Pop', 'Marian', 7500);
END;
/
select * from emp_sal;

2. Să se adauge o nouă înregistrare în tabela PRODUSE prin introducerea valorilor cu


ajutorul variabilelor de substituţie:

- 23 -
Baze de date. Limbajul PL/SQL

BEGIN
INSERT INTO produse (id_produs, denumire_produs,
categorie, stoc)
VALUES (&id, '&denumire', '&categorie', &stoc);
END;
/
select * from produse;

3. Să se adauge clientul Ionescu George. Datele personale (telefon, nivelul veniturilor şi


emailul) se vor citi de la tastatură. Id-ul acestuia va avea valoarea ultimului id de client
incrementat cu o unitate.
set serveroutput on
declare
v_id clienti.id_client% TYPE;
begin
select max(id_client) into v_id from clienti;
insert into clienti
values(v_id+1,'&prenume','&nume',&tel,
NULL,'&email',NULL, NULL, NULL, &venit);
/

Comanda UPDATE:
4. Să se mărească cu un procent salariul angajaţilor din tabela EMP_SAL care au în
prezent salariul mai mic decât valoare variabilei v_prag:
DECLARE
v_procent number:=0.1;
v_prag angajati.salariul%type:=10000;
BEGIN
UPDATE emp_sal
SET salariul=salariul*(1+v_procent)
WHERE salariul<v_prag;
END;
/
select * from emp_sal;

5. Să se realizeze o aprovizionare în cadrul depozitului prin care se măresc socurile


tuturor produselor cu 100 bucăţi:
BEGIN
UPDATE produse
SET stoc=nvl(stoc,0)+100;
END;
/
select * from produse;

- 24 -
Baze de date. Limbajul PL/SQL

Figura 11 –Execuţia blocului PL/SQL

6. În urma comenzilor realizate de clienţi, monitoarele de tipul LCD sunt vândute, deci se
scade din stocul existent un număr de monitoare introdus de la tastatură:
BEGIN
UPDATE produse
SET stoc=stoc-&nr_buc_vandute
WHERE lower(denumire_produs) like 'monitor lcd%';
COMMIT;
END;
/
select * from produse;

- 25 -
Baze de date. Limbajul PL/SQL

Figura 12 –Execuţia blocului PL/SQL

Comanda DELETE:
7. Să se şteargă angajatul cu numele Pop din tabela EMP_SAL:
DECLARE
BEGIN
DELETE FROM emp_sal WHERE initcap(nume) like 'Pop%';
ROLLBACK;
END;
/
select * from emp_sal;

Atenţie! În cazurile de mai sus utilizarea comenzilor COMMIT respectiv ROLLBACK vor
avea efect atât asupra tranzacţiei curente cât şi asupra tranzacţiilor executate anterior, în afara
blocului.

EXERCIŢII PROPUSE

1. Creaţi un bloc PL/SQL prin care să se adauge un produs nou în tabela PRODUSE.
a. Folosiţi maximul dintre codurile produselor si adăugaţi 10 la această valoare,
folosind-o ca valoare pentru codul produsului nou introdus.
b. Folosiţi parametri (variabile de substituţie) pentru denumire, categoria şi stocul
produsului.
c. Lăsaţi descrierea produsului NULL.
d. Executaţi blocul.

- 26 -
Baze de date. Limbajul PL/SQL

2. Creaţi un bloc PL/SQL prin care să se selecteze stocul maxim pentru produsele existente în
tabela PRODUSE. Afişaţi rezultatul pe ecran.

3. Creaţi un bloc PL/SQL prin care să se şteargă un produs pe baza codului acestuia primit ca
parametru (variabilă de substituţie). Anulaţi ştergerea dintr-un alt bloc PL/SQL.

- 27 -
Baze de date. Limbajul PL/SQL

CAPITOLUL IV.
STRUCTURI FUNDAMENTALE DE PROGRAMARE

Limbajul PL/SQL suportă toate tipurile de structurile de programare, punând la


dispoziţia programatorului instrucţiuni decizionale (IF, CASE) şi repetitive (LOOP, WHILE,
FOR).

IV.1. STRUCTURI ALTERNATIVE


Structura IF..THEN..END IF este următoarea:
IF cond1 THEN
secvcom1;
ELSE
secvcom2;
END IF;
Se poate utiliza varianta imbricată:
IF cond1 THEN
secvcom1;
ELSE
IF cond2 THEN
secvcom2;
END IF;
END IF;

În cazul imbricării se poate folosi clauza ELSIF în loc de mai multe instrucţiuni IF
imbricate. Instrucţiunea este similară cu o instrucţiune de tip CASE.
IF cond1 THEN
secvcom1;
ELSIF cond2 THEN secvcom2;
ELSIF cond3 THEN secvcom3;
---------------------------------------
ELSIF condN THEN secvcomN;
ELSE secvcomN+1;
END IF;

Exemplu: În funcţie de valoarea stocului produsului cu codul 3133, acesta se va afişa


modificat pe ecran prin intermediul variabilei v_stoc.
DECLARE
v_stoc produse.stoc%type;
BEGIN
SELECT stoc into v_stoc from produse where
id_produs=3133;
dbms_output.put_line ('Stocul initial este: '||v_stoc);
IF v_stoc < 500 THEN
v_stoc:=2* v_stoc;
ELSIF v_stoc between 500 and 1000 THEN
v_stoc:=1.5 * v_stoc;
ELSE
- 28 -
Baze de date. Limbajul PL/SQL

v_stoc:=1.25* v_stoc;
END IF;
dbms_output.put_line('Stocul final este: '||v_stoc);
end;
/

Figura 13 –Execuţia blocului PL/SQL

Atenţie la variabilele NULL şi evaluarea în IF!

De exemplu, în următoarea situaţie se va afişa “Felicitări, sunteţi admis!” din cauza


faptului că variabila nota este declarată, dar nu este iniţializată, fiind deci NULL:
DECLARE
nota number;
Begin
IF nota<5 THEN
dbms_output.put_line('Ne pare rau, candidatul este
respins!');
ELSE
dbms_output.put_line('Felicitari, sunteti admis!');
END IF;
END;
/

- 29 -
Baze de date. Limbajul PL/SQL

Figura 14 –Execuţia blocului PL/SQL

Observaţi şi cazurile de mai jos:


DECLARE
X number;
Y number;
BEGIN
X:=10;
IF x!=y then
--întoarce NULL şi nu TRUE
END IF;
END;
sau
DECLARE
a number;
b number;
BEGIN
IF a=b then
--întoarce NULL şi nu TRUE
END IF;
END;

Structura CASE ... WHEN... THEN...


Se pot utiliza două variante: expresii CASE (CASE Expressions) care întorc un
rezultat într-o variabilă şi structura CASE (CASE Statement) care execută instrucţiuni pe
fiecare ramură decizională.
Expresia CASE (CASE Expression) are următoarea sintaxă:
Variabila:=
CASE [Selector]
WHEN expression1 THEN result1
- 30 -
Baze de date. Limbajul PL/SQL

WHEN expression2 THEN result2


-----------------------------------------
WHEN expressionN THEN resultN
[ELSE result N+1]
END;

Exemplu: În funcţie de valoarea stocului produsului cu codul 3133, acesta se va afişa


modificat pe ecran prin intermediul variabilei v_stoc.
DECLARE
v_stoc produse.stoc%type;
v_proc number(4,2);
BEGIN
SELECT stoc into v_stoc from produse where
id_produs=3133;
dbms_output.put_line('Stocul initial este: '||v_stoc);
v_proc:=
CASE
when v_stoc < 500 THEN 2
when v_stoc between 500 and 1000 THEN 1.5
else 1.25
END;
v_stoc:= v_stoc *v_proc;
dbms_output.put_line('Salariul final este: '|| v_stoc);
end;
/

Structura CASE (CASE Statement) are următoarea sintaxă:


CASE [Selector]
WHEN expression1 THEN action1;
WHEN expression2 THEN action2;
-----------------------------------------
WHEN expressionN THEN actionN;
[ELSE action N+1];
END CASE;

Exemplu: În funcţie de valoarea stocului produsului cu codul 3133, acesta se va afişa


modificat pe ecran prin intermediul variabilei v_stoc.
DECLARE
v_stoc produse.stoc%type;
BEGIN
SELECT stoc into v_stoc from produse where
id_produs=3133;
dbms_output.put_line('Stocul initial este: '||v_stoc);
CASE
when v_stoc < 500 THEN v_stoc:= v_stoc *2;
when v_stoc between 500 and 1000 THEN v_stoc:= v_stoc
*1.5;
else v_stoc:= v_stoc *1.25;
END CASE;
dbms_output.put_line('Salariul final este: '|| v_stoc);
end;
/

- 31 -
Baze de date. Limbajul PL/SQL

IV.2. STRUCTURI REPETITIVE


Se pot utiliza trei tipuri de structuri repetitive:
Structura LOOP……END LOOP are următoarea sintaxă:
LOOP
Secventa comenzi;
EXIT [WHEN cond];
END LOOP;

Exemple:
1. Să se afişeze pe ecran utilizând structura LOOP…END LOOP numerele 9,7, 4, 0.
set serveroutput on
DECLARE
v_nr number(2):=10;
i number(2):=1;
BEGIN
loop
v_nr:=v_nr-i;
i:=i+1;
exit when v_nr < 0;
dbms_output.put_line(v_nr);
end loop;
END;
/

2. Utilizând un tip de dată record de acelaşi tip cu DEPARTAMENTE, să se afişeze


denumirea fiecărui departament cu id-ul: 10, 20, 30, 40, 50.
DECLARE
vrec_dep departamente%rowtype;
i number:=10;
BEGIN
loop
SELECT * into vrec_dep from departamente where
id_departament=i;
dbms_output.put_line('Departamentul: '||
vrec_dep.id_departament|| ' are denumirea de:
'||vrec_dep.denumire_departament);
exit when i>=50;
i:=i+10;
end loop;
END;
/

- 32 -
Baze de date. Limbajul PL/SQL

Figura 15 –Execuţia blocului PL/SQL

Structura WHILE…..LOOP….END LOOP are următoarea structură


WHILE cond LOOP
Secventa comenzi 1;
Secventa comenzi 2;
EXIT [WHEN cond];
END LOOP;

Exemple:
1. Să se afişeze pe ecran utilizând structura WHILE LOOP…END LOOP numerele
9,7, 4, 0.
set serveroutput on
DECLARE
v_nr number(2):=10;
i number(2):=1;
BEGIN
while v_nr > 0 loop
v_nr:=v_nr-i;
i:=i+1;
dbms_output.put_line(v_nr);
end loop;
END;
/

2. Să se afişeze în ordine angajaţii cu codurile în intervalul 100-110 atât timp cât


salariul acestora este mai mic decât salariul mediu:
DECLARE
v_sal angajati.salariul%type;
v_salMediu v_sal%type;
- 33 -
Baze de date. Limbajul PL/SQL

i number(4):=100;
BEGIN
--calculam salariul mediu:
SELECT avg(salariul) into v_salmediu from angajati;
dbms_output.put_line('Salariul mediu este:
'||v_salmediu);
while i<=110 loop
select salariul into v_sal from angajati where
id_angajat=i;
dbms_output.put_line('Salariatul cu codul '||i||' are
salariul: '||v_sal);
i:=i+1;
--iesim din ciclul WHILE la indeplinirea conditiilor
exit when v_sal<v_salmediu;
end loop;
end;
/

Figura 16 –Execuţia blocului PL/SQL

Structura FOR…..LOOP….END LOOP are următoarea structură:


FOR var IN [REVERSE] valmin..valmax LOOP
Secventa comenzi;
EXIT [WHEN cond];
END LOOP;

Observaţie: Valorile intervalului pot fi de orice tip, dar sa aibă valori care pot fi
convertite la un intreg (de exemplu 20/13 sau 11/5). Dacă aceste 2 valori vor fi egale ca
intregi atunci instrucţiunile din interiorul ciclului se execută o singură dată, ca în exemplul
următor:
FOR i IN 3..3 LOOP
- 34 -
Baze de date. Limbajul PL/SQL

Secventa comenzi;
END LOOP;

Exemple:
1. Să se încarce în tabela MESAJE numere de la 1…10 cu excepţia lui 6 şi 8.
create table mesaje (rez varchar2(30));
begin
for i in 1..10 loop
if i=6 or i=8 then
null;
else
insert into mesaje(rez) values (i);
end if;
commit;
end loop;
end;
/

2. Să se afişeze numărul de comenzi ale fiecărui angajat al cărui id este situat în


intervalul 155..160, dar să se întrerupă afişarea în cazul în care se găseşte primul angajat din
acest interval care nu are nicio comandă:
DECLARE
v_nr number;
v_nume angajati.nume%type;
v_id angajati.id_angajat%type;
BEGIN
for v_id in 155..160 loop
v_nr:=0;
SELECT count(c.nr_comanda) into v_nr from comenzi
c,angajati a
where c.id_angajat=a.id_angajat and a.id_angajat=v_id;
dbms_output.put_line('Salariatul cu id-ul: '||v_id||'
are: '||v_nr||' comenzi');
exit when v_nr=0;
end loop;
end;
/

- 35 -
Baze de date. Limbajul PL/SQL

Figura 17 –Execuţia blocului PL/SQL

Structurile LOOP se pot imbrica, în acest caz fiind recomandat să se eticheteze


ciclurile LOOP, astfel:
BEGIN
<<LOOP_EXTERN>>
LOOP
v_var:=v_var+1;
EXIT WHEN v_var>10;
<<LOOP_INTERN>>
LOOP
……………………….
EXIT LOOP_EXTERN WHEN cond1;
EXIT WHEN cond2;
…………………………
END LOOP;
…………………………….
END LOOP;
END;

EXERCIŢII PROPUSE

1. Utilizând structuri alternative, rezolvaţi ecuaţia de gradul 2.

2. Afişaţi numele şi telefonul aferente fiecărui client cu id-ul număr par.

3. Construiţi un bloc anonim pentru a exemplifica structurile LOOP imbricate.

- 36 -
Baze de date. Limbajul PL/SQL

CAPITOLUL V.
CURSORI ŞŞII TABELE INDEX BY

Cursorul reprezintă o variabilă PL/SQL specială utilizată atunci când se execută o


comandă SQL, iar Oracle Server deschide o zonă de memorie (context area) în care comanda
este executată. Cursorul este un pointer către această zonă.
În PL/SQL se utilizează două tipuri de variabile de tip cursor: cursorul implicit
declarat pentru toate instrucţiunile de manipulare (INSERT/UPDATE/DELETE); cursorul
explicit declarat şi gestionat de programator pentru utilizarea comenzii SELECT în cazul în
care aceasta returnează mai multe înregistrări.

V.1. CURSORUL IMPLICIT


Cursorul implicit este declarat de Oracle Server pentru toate comenzile de manipulare
a datelor (INSERT, UPDATE, DELETE) prezente într-un bloc PL/SQL. Dacă o instrucţiune
DML nu afectează nicio linie a tabelei nu se generează eroare, însă excepţia trebuie tratată
folosind atributele speciale ale cursorilor.
Cursorul implicit prezintă o serie de atribute care pot fi utilizate într-un bloc PL/SQL
pentru verificarea execuţiei instrucţiunilor DML. Aceste atribute sunt:
 SQL%ROWCOUNT- reprezintă numărul liniilor returnate până în momentul
curent;
 SQL%NOTFOUND – evaluat la TRUE în cazul în care cea mai recentă
instrucţiune de manipulare nu a afectat nicio linie;
 SQL%FOUND – opusul lui SQL%NOTFOUND.

Exemple:
1. Să se şteargă un produs din tabela PRODUSE şi să se contorizeze numărul de
rânduri şterse.
SET SERVEROUTPUT ON
DECLARE
v_rez NUMBER(2);
BEGIN
DELETE FROM produse
WHERE id_produs=111;
v_rez:=SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE (v_rez || ' randuri sterse');
COMMIT;
END;
/

2. Să se încerce modificarea denumirii produsului cu codul 3, în cazul în care acest


produs nu există (comanda update nu afectează nicio înregistrare) va fi afişat un mesaj
corespunzător.
BEGIN
UPDATE produse
SET denumire_produs='cafea'
WHERE id_produs=3;

- 37 -
Baze de date. Limbajul PL/SQL

IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('Nu exista produsul cu acest
cod');
END IF;
END;
/

Figura 18 –Execuţia blocului PL/SQL

3. Să se şteargă din tabela EMP_SAL salariatul al cărui ID este introdus de utilizator


prin intermediul variabilei g_angid:
ACCEPT g_angid PROMPT 'Introduceti id-ul salariatului:'
VARIABLE nr_sters varchar2(100)
DECLARE
BEGIN
DELETE FROM emp_sal WHERE id_angajat=&g_angid;
:nr_sters:=TO_CHAR(SQL%ROWCOUNT)||' INREGISTRARI
STERSE';
END;
/
PRINT nr_sters
Rollback;

- 38 -
Baze de date. Limbajul PL/SQL

Figura 19 –Execuţia blocului PL/SQL

V.2. CURSORUL EXPLICIT


Cursorul explicit se utilizează pentru a procesa individual fiecare linie (înregistrare)
returnată de o instrucţiune SELECT ce returnează mai multe înregistrări. Mulţimea
înregistrărilor returnate de o cerere este numită mulţime rezultat. Cursorul păstrează un
pointer către linia curentă în cadrul unei mulţimi rezultat.
Verificarea stării unui cursor explicit se realizează prin intermediul următoarelor
atribute:
 nume_cursor%ISOPEN - evaluat la TRUE în cazul în care cursorul este deschis;
 nume_cursor %NOTFOUND - evaluat la TRUE în cazul în care cel mai recentă
citire a valorilor din cursor nu a returnat nicio linie;
 nume_cursor %FOUND - opusul lui %NOTFOUND;
 nume_cursor %ROWCOUNT - are ca valoare numărul liniilor returnate până în
momentul curent.
Prelucrarea cursorului explicit presupune parcurgerea următoarelor etape:
 se declară variabilele în care vor fi încărcate valorile corespunzătoare unei linii din
cursor;
 se declară cursorul explicit, specificându-se un nume pentru acesta şi definindu-se
interogarea de procesat în cadrul lui:
nume_cursor IS SELECT. ..................... ;
 se deschide cursorul prin intermediul instrucţiunii OPEN, care execută interogarea
şi legarea tuturor variabilelor referite. Înregistrările returnate de interogare sunt
desemnate drept set activ de date, care pot fi de acum încărcate.
OPEN nume_cursor;
 utilizându-se instrucţiunea FETCH, se citeşte linia curentă din cursor şi se atribuie
valorile variabilor. Fiecare încărcare determină mutarea pointerului cursorului la
linia următoare din setul activ de date.
- 39 -
Baze de date. Limbajul PL/SQL

FETCH nume_cursor INTO var1, var2,. ............;


 este închis cursorul prin instructiunea CLOSE, care dezafectează setul activ de linii.
Cursorul poate fi din nou deschis pentru a stabili un nou set activ de linii.
CLOSE nume_cursor;
Pentru a procesa liniile unui cursor explicit se utilizează de obicei o structură repetitivă
pentru executarea unei încărcări a variabilor pe baza valorilor din cursor (FETCH) în fiecare
iteraţie. În final, toate liniile din setul activ sunt procesate şi o comandă FETCH executată
fără succes poziţionează atributul %NOTFOUND pe TRUE.
Înaintea primei citiri din cursor (prima comandă FETCH) valoarea
cursor%NOTFOUND se evaluează la NULL, ca şi în cazul în care comanda FETCH nu se
execută niciodată cu succes.

Exemple:
1. Să se afişeze lista cu numele şi salariul angajaţilor din departamentul 60 folosind
un cursor explicit:
set serveroutput on
DECLARE
cursor ang_cursor is select id_angajat, nume, salariul
from angajati where id_departament=60;
ang_id angajati.id_angajat%type;
ang_nume angajati.nume%type;
ang_sal angajati.salariul%type;
BEGIN
dbms_output.put_line('Lista cu salariariile angajatilor
din departamentul 60');
open ang_cursor;
loop
fetch ang_cursor into ang_id, ang_nume, ang_sal;
exit when ang_cursor%notfound;
dbms_output.put_line('Salariatul '||ang_nume||' are
salariul: '||ang_sal);
end loop;
close ang_cursor;
end;
/

- 40 -
Baze de date. Limbajul PL/SQL

Figura 20 –Execuţia blocului PL/SQL

Pentru o flexibilitate mai mare se poate utiliza o variabilă de tip record pentru
încărcarea valorilor din cursor. Această variabilă de tip record poate avea aceleaşi atribute ca
şi cursorul prin specificarea proprietăţii %ROWTYPE. În acest caz încărcarea din cursor se va
face direct prin instrucţiunea FECH var_cursor INTO var_record.

Exemplul de mai sus poate fi rescris astfel:


set serveroutput on
declare
cursor ang_cursor is select id_angajat, nume, salariul
from angajati where id_departament=60;
--tipul record pt incarcarea valorilor cursorului
ang_rec ang_cursor%rowtype;
begin
dbms_output.put_line('Lista cu salariariile angajatilor
din departamentul 60');
open ang_cursor;
loop
fetch ang_cursor into ang_rec;
exit when ang_cursor%notfound;
dbms_output.put_line('Salariatul '||ang_rec.nume||' are
salariul: '||ang_rec.salariul);
end loop;
close ang_cursor;
end;
/

2. Să se încarce în tabela T_ANG primii 5 angajaţi (id şi nume)


-- se creează tabela mesaje
CREATE TABLE T_ANG
- 41 -
Baze de date. Limbajul PL/SQL

(cod varchar2(7),
nume varchar2(20));

DECLARE
v_id angajati.id_angajat%type;
v_nume angajati.nume%type;
CURSOR c1 IS SELECT id_angajat, nume FROM angajati;
BEGIN
OPEN c1;
FOR i IN 1..5 LOOP
FETCH c1 INTO v_id, v_nume;
INSERT INTO t_ang VALUES(v_id, v_nume);
END LOOP;
CLOSE c1;
END;
/
SELECT * FROM T_ANG;

Figura 21 –Execuţia blocului PL/SQL

Testul de ieşire din ciclul LOOP în acest caz se poate face şi cu ajutorul
atributului %ROWCOUNT:
--se şterg înregistrările din tabela T_ANG:
Delete from T_ANG;
DECLARE
v_id angajati.id_angajat%type;
v_nume angajati.nume%type;
CURSOR c1 IS SELECT id_angajat, nume FROM angajati;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_id, v_nume;

- 42 -
Baze de date. Limbajul PL/SQL

EXIT WHEN c1%ROWCOUNT>5 OR c1%NOTFOUND;


INSERT INTO T_ANG VALUES (v_id, v_nume);
END LOOP;
CLOSE c1;
END;
/
SELECT * FROM T_ANG;

3. Să se afişeze primele 3 comenzi care au cele mai multe produse comandate. În acest
caz înregistrările vor fi ordonate descrescător în funcţie de numărul produselor comandate:
SET SERVEROUTPUT ON
DECLARE
CURSOR c_com IS
select c.nr_comanda, count(r.id_produs) Numar
from comenzi c, rand_comenzi r
where c.nr_comanda=r.nr_comanda
group by c.nr_comanda
order by count(r.id_produs) desc;
rec_com c_com%rowtype;
BEGIN
DBMS_OUTPUT.PUT_LINE('Numarul de produse pentru fiecare
comanda:');
IF NOT c_com%ISOPEN THEN
OPEN c_com;
END IF;
LOOP
FETCH c_com INTO rec_com;
EXIT WHEN c_com%NOTFOUND OR c_com%ROWCOUNT>3;
DBMS_OUTPUT.PUT_LINE('Comanda '||rec_com.nr_comanda||'
are: '||rec_com.numar||' produse');
END LOOP;
CLOSE c_com;
END;
/

- 43 -
Baze de date. Limbajul PL/SQL

Figura 22 –Execuţia blocului PL/SQL

4. Să se afişeze cota de impozitare şi valoarea impozitului plătit de primii n angajaţi.


Se va utiliza un cursor, iar n se citeşte de la tastatură.
set serveroutput on;
DECLARE
CURSOR c is select nume,salariul from angajati;
var c%rowtype;
imp number(4,2);
val angajati.salariul%type;
BEGIN
OPEN c;
LOOP
FETCH c INTO var;
imp:= CASE
when var.salariul<1000 THEN 0.5
when var.salariul between 1000 AND 3000 then 1.25
when var.salariul between 3000 and 5000 then 1.5
when var.salariul between 500 and 7000 then 1.75
else 2
END;
val:=imp*var.salariul;
DBMS_OUTPUT.PUT_LINE('Angajatul '||var.nume||' are
impozitul pe salariu de '||imp||'% - '||val||' RON');
EXIT WHEN c%rowcount>&n OR c%notfound;
END LOOP;
CLOSE c;
END;
/

- 44 -
Baze de date. Limbajul PL/SQL

Figura 23 –Execuţia cursorului pentru n=7

Gestiunea implicită a cursorului prin utilizarea unui ciclu FOR:

FOR nume_record IN nume_cursor LOOP


----------------------------------------------
END LOOP;

În acest caz, tipul RECORD nu trebuie declarat. Se realizează în mod implicit


deschiderea (OPEN), încărcarea (FETCH) şi închiderea cursorului (CLOSE).

Exemplu:
Să se afişeze printr-un ciclu FOR numele şi salariile angajaţilor din departamentul 50:
set serveroutput on
declare
cursor ang_cursor is select id_angajat, nume, salariul
from angajati where id_departament=50;
begin
dbms_output.put_line('Lista cu salariariile angajatilor
din departamentul 50');
for ang_rec in ang_cursor loop
dbms_output.put_line('Salariatul '||ang_rec.nume||' are
salariul: '||ang_rec.salariul);
end loop;
end;
/

- 45 -
Baze de date. Limbajul PL/SQL

Figura 24 –Execuţia blocului PL/SQL

Utilizarea unui cursor direct în cadrul instrucţiunii FOR.


În acest caz cursorul nu este declarat, nu are nume, este reprezentat doar de interogarea
SELECT din cadrul instrucţiunii FOR, astfel:

FOR NUME_RECORD IN (SELECT......) LOOP


....................................................
END LOOP;

Dezavantajul în acest caz este ca nu se pot utiliza atributele cursorului din cauza
faptului că acesta nu are nume.

Exemplu:
Să se afişeze suma aferentă salariilor din fiecare departament:
set serveroutput on
declare
begin
dbms_output.put_line('Total salarii pe fiecare
departament:');
for dep_rec in
(select d. id_departament dep, sum(a.salariul) sal
from angajati a, departamente d
where a.id_departament=d.id_departament
group by d.id_departament)
loop
dbms_output.put_line('Departamentul '||dep_rec.dep||'
are de platit salarii in valoare de: '||dep_rec.sal||'
RON');

- 46 -
Baze de date. Limbajul PL/SQL

end loop;
end;
/

Figura 25 –Execuţia blocului PL/SQL

Utilizarea cursorului cu parametru


Pentru o flexibilitate mai mare se pot declara şi utiliza cursori cu parametru care
transmit valorile parametrilor actuali în cererile SQL. Declararea cursorului cu parametru se
face astfel:

Cursor nume_cursor (parametru1 tip_data,. )


Is select................. ;

Deschidere:
Open nume_cursor(valoare_parametru1,. .... );

Cursorii parametrizaţi nu oferă o funcţionalitate suplimentară ci doar o modalitate


simplă şi clară de a specifica valori de intrare.
Tipurile parametrilor sunt scalare, dar nu li se precizează dimensiunea, acestea fiind
referite în interogare.

Exemple:
1. Să se afişeze produsele al căror cantitate totală comandată este mai mare decât o
valoare primită drept parametru.
SET SERVEROUTPUT ON
DECLARE
CURSOR c_prod (p_val NUMBER) IS
SELECT p.id_produs, p.denumire_produs, sum(r.cantitate)
total
- 47 -
Baze de date. Limbajul PL/SQL

FROM produse p, rand_comenzi r


WHERE p.id_produs =r.id_produs
GROUP BY p.id_produs, p.denumire_produs
HAVING sum(r.cantitate)>p_val
ORDER BY total desc;

v_val NUMBER(5);
rec_prod c_prod%rowtype;

BEGIN
v_val:=500;
DBMS_OUTPUT.PUT_LINE('Produsele al caror cantitate
vândută este mai mare decat '|| v_val);

IF NOT c_prod%ISOPEN THEN


OPEN c_prod (v_val);
END IF;
LOOP
FETCH c_prod into rec_prod;
EXIT WHEN c_prod%notfound;
DBMS_OUTPUT.PUT_LINE('Din produsul
'||rec_prod.id_produs||',
'||rec_prod.denumire_produs||', s-au vandut '
||rec_prod.total||' unitati');
END LOOP;
CLOSE c_prod;
END;
/

Figura 26 –Execuţia blocului PL/SQL

- 48 -
Baze de date. Limbajul PL/SQL

2. Să se afişeze pentru fiecare comandă produsele comandate. În acest caz se


utilizează două variabile de tip cursor:
SET SERVEROUTPUT ON
DECLARE
--cursorul care va prelua comenzile incheiate
CURSOR c_com IS
SELECT nr_comanda, data
FROM comenzi
Where modalitate= 'online'
ORDER BY nr_comanda;

--cursorul care, pentru fiecare comanda, va afisa


produsele din cadrul acesteia, ordonate descrescator
CURSOR c_prod (p_nr_comanda NUMBER) IS
SELECT r.id_produs, p.denumire_produs, r.cantitate
FROM produse p, rand_comenzi r
WHERE p.id_produs=r.id_produs
AND r.nr_comanda=p_nr_comanda
ORDER BY r.id_produs desc;

rec_com c_com%rowtype; --variabila record pentru


campurile din primul cursor
rec_prod c_prod%rowtype; --variabila record pentru
campurile din al doilea cursor

BEGIN
OPEN c_com;
LOOP
FETCH c_com into rec_com;
EXIT WHEN c_com%notfound;
DBMS_OUTPUT.PUT_LINE('Comanda '|| rec_com.nr_comanda
||' incheiata la data de '||rec_com.data);

OPEN c_prod (rec_com.nr_comanda); --cursorul primeste


drept parametru numarul comenzii care a fost afisata
LOOP
FETCH c_prod into rec_prod;
EXIT WHEN c_prod%notfound;
DBMS_OUTPUT.PUT_LINE('Din produsul
'||rec_prod.id_produs||',
'||rec_prod.denumire_produs||', s-au comandat '
||rec_prod.cantitate||' bucati');
END LOOP;
CLOSE c_prod;

DBMS_OUTPUT.PUT_LINE('============');
END LOOP;
CLOSE c_com;
END;
/

- 49 -
Baze de date. Limbajul PL/SQL

Figura 27 –Execuţia blocului PL/SQL

3. Să se afişeze denumirea funcţiei şi departamentul în care lucrează primii n angajaţi. Se va


utiliza cursor cu parametru, iar n este citit de la tastatură.
set serveroutput on;
DECLARE
CURSOR c is select nume, salariul, id_departament,
id_functie from angajati;
CURSOR c1(id_dep angajati.id_departament%type)
is select denumire_departament from departamente
where id_dep=id_departament;
CURSOR c2(id_funct angajati.id_functie%type)
is select denumire_functie from functii
where id_funct=id_functie;
BEGIN
FOR var IN c LOOP
DBMS_OUTPUT.PUT_LINE('Angajatul :'||var.nume||'
salariul :'||var.salariul);
FOR var1 IN c1(var.id_departament) LOOP
DBMS_OUTPUT.PUT_LINE('Este in departamentul'
||var1.denumire_departament);
END LOOP;
FOR var2 IN c2(var.id_functie) LOOP
DBMS_OUTPUT.PUT_LINE('Are
functia'||var2.denumire_functie);
END LOOP;
EXIT WHEN c%notfound or c%rowcount>&n;
END LOOP;
END;
/
- 50 -
Baze de date. Limbajul PL/SQL

Figura 28 –Execuţia cursorului pentru n=5

4. Să se afişeze produsele şi cantităţile cumpărate de către clienţi.


set serveroutput on;
DECLARE
CURSOR c is select nume_client, id_client from clienti;
CURSOR c1(id_c clienti.id_client%type) is
select c.nr_comanda,c.modalitate,p.denumire_produs,
r.cantitate
from comenzi c, rand_comenzi r, produse p
where id_c=c.id_client
and r.nr_comanda= c.nr_comanda
and r.id_produs= p.id_produs
order by c.modalitate;
BEGIN
FOR var IN c LOOP
DBMS_OUTPUT.PUT_LINE('Clientul: '||var.nume_client||' a
achizitionat urmatoarele produse:');
FOR var1 IN c1(var.id_client) LOOP
DBMS_OUTPUT.PUT_LINE(var1.denumire_produs||'
'||var1.modalitate||' '||var1.cantitate);
END LOOP;
EXIT WHEN c%notfound;
END LOOP;
END;
/

- 51 -
Baze de date. Limbajul PL/SQL

Figura 29 –Execuţia blocului PL/SQL

Actualizarea înregistrărilor returnate de cererea cursorului. Clauza FOR UPDATE


Pentru asigurarea consistenţei şi concurenţei la date, actualizarea înregistrărilor din
tabele care sunt încărcate în cursorul explicit se poate face prin blocarea setului de înregistrări
în 2 variante: NOWAIT şi WAIT n.
CURSOR C IS SELECT .... FROM....
FOR UPDATE [OF COLUMN_NAME] [NOWAIT|WAIT n];
Clauza FOR UPDATE din interogarea asociată cursorului se adaugă pentru a bloca
liniile afectate atunci când cursorul este deschis. Clauza NOWAIT determină apariţia unei
erori dacă liniile sunt blocate de o altă sesiune. Atunci când mai multe tabele sunt implicate în
interogare, se poate folosi FOR UPDATE pentru a impune blocarea liniilor unei tabele
anume. Liniile unei tabele sunt blocate numai în cazul în care clauza FOR UPDATE face o
referire la o coloană din acea tabelă.
Exemplu:
Se creează tabela SITUATIE care stochează informaţii referitoare la comenzi: codul,
valoarea comenzii. Se adaugă în aceasta coloana TVA, care va păstra valoarea cotei TVA
(19%) pentru fiecare comandă. Se creează un cursor căruia i se adaugă clauza FOR
UPDATE pentru a bloca liniile afectate din tabelă, atunci când cursorul este deschis, iar
pentru fiecare comandă din cursor se va calcula valoarea TVA.
DROP TABLE situatie;
CREATE TABLE situatie AS
SELECT c.nr_comanda cod, SUM(r.cantitate*r.pret) as
valoare
FROM comenzi c, rand_comenzi r
WHERE c. nr_comanda =r. nr_comanda
AND c.modalitate= 'online'
GROUP BY c. nr_comanda;

ALTER TABLE situatie


- 52 -
Baze de date. Limbajul PL/SQL

ADD(tva NUMBER(10));

DECLARE
CURSOR c_situatie IS
SELECT cod, valoare, tva
FROM situatie
FOR UPDATE OF tva NOWAIT;

BEGIN
FOR rec_situatie IN c_situatie LOOP

UPDATE situatie
SET tva=valoare*0.19
WHERE cod=rec_situatie.cod;

DBMS_OUTPUT.PUT_LINE('Comanda '||rec_situatie.cod||'
are valoarea totala de '||rec_situatie.valoare||' RON
si TVA de: '||rec_situatie.tva );
END LOOP;
END;
/
SELECT * FROM situatie;

Figura 30 –Execuţia blocului PL/SQL

Atenţie: În cazul de mai sus se poate observa faptul că atributul REC_SITUATIE.TVA


este NULL după execuţia comenzii UPDATE deoarece nu se actualizează automat şi cursorul
odată cu tabela. Acesta trebuie închis şi redeschis pentru a fi vizibile actualizările din tabelă.
Pentru manipularea cât mai uşoară a comenzilor DML UPDATE şi DELETE se poate
utiliza clauza WHERE CURRENT OF care permite actualizarea înregistrărilor pe baza liniei
curente din cursor.

- 53 -
Baze de date. Limbajul PL/SQL

UPDATE tabela
SET camp=....
WHERE CURRENT OF nume_cursor;

Se poate referi linia din tabela originară, pentru actualizare sau ştergere, prin
intermediul liniei curente a cursorului (cea procesată de ultima instrucţiune FETCH). Clauza
FOR UPDATE trebuie inclusă în definiţia cursorului pentru a bloca liniile în momentul
execuţiei instrucţiunii OPEN.
Exemplu:
Exemplul de mai sus poate fi rescris, actualizarea înregistrărilor din tabela SITUATIE
realizându-se cu clauza WHERE CURRENT OF:

DROP TABLE situatie;


CREATE TABLE situatie AS
SELECT c.nr_comanda cod, SUM(r.cantitate*r.pret) as
valoare
FROM comenzi c, rand_comenzi r
WHERE c. nr_comanda =r. nr_comanda
AND c.modalitate= 'online'
GROUP BY c. nr_comanda;

ALTER TABLE situatie


ADD(tva NUMBER(10));

DECLARE
CURSOR c_situatie IS
SELECT cod, valoare, tva
FROM situatie
FOR UPDATE OF tva NOWAIT;

BEGIN
FOR rec_situatie IN c_situatie LOOP

UPDATE situatie
SET tva=valoare*0.19
WHERE CURRENT OF c_situatie;

DBMS_OUTPUT.PUT_LINE('Comanda '||rec_situatie.cod||'
are valoarea totala de '||rec_situatie.valoare||' RON
si tva de: '||rec_situatie.tva );
END LOOP;
END;
/

SELECT * FROM situatie;

- 54 -
Baze de date. Limbajul PL/SQL

V.3. TIPURI DE TABELE INDEX BY

Tipul de date se utilizează similar cu o matrice şi este un tip de date definit de


utilizator astfel:
TYPE nume_tab IS TABLE OF {TIP_DATA [variabila%type |
tabela.coloana%type [NOT NULL]| tabela%rowtype}
INDEX BY PLS_INTEGER|BINARY_INTEGER|VARCHAR2(dimensiune);
Declararea unei variabile de acest tip se face astfel:
v_tab nume_tab;

Adresarea elementelor tabelei se realizează cu v_tab(index).câmp. Spre deosebire de


prelucrările pe matrici, în cazul acestui tip de date indexul este unic, dar în ordine aleatorie şi
poate fi negativ situat între limitele intervalului PLS_INTEGER de (-2147483647,
2147483647)
Se pot utiliza urmatoarele proprietăţi şi metode:
v_tab.EXISTS(i) – returnează TRUE dacă elementui i există şi are valoare;
v_tab.COUNT – returnează numărul de elemente din variabilă;
v_tab. FIRST şi v_tab.LAST – returnează indexul primei, respectiv ultimei
linii din variabilă;
v_tab. PRIOR(i) şi v_tab.NEXT(i) – returnează precedentul, respectiv
următorul element al lui i;
v_tab.DELETE sau v_tab.DELETE(i) sau v_tab.DELETE(i,j) – şterge
elementul curent, respectiv elementul i, sau elementele i-j.

Exemple:
1. Să se utilizeze o tabelă cu elemente de tipul produse.denprodus%type pentru
parcurgerea produselor cu codul între 2252 şi 2255:
DECLARE
--declararea tipului:
type num_table is table of produse.denumire_produs%type
index by pls_integer;
v_tab num_table;
i number(5):=2252;
BEGIN
--incarcarea in tabela de tipul index by:
loop
SELECT denumire_produs into v_tab(i) from produse where
id_produs=i;
i:=i+1;
exit when i>2255;
end loop;
--extragerea din tabela de tipul index by:
for i in v_tab.first..v_tab.last loop
IF v_tab.EXISTS(i) then
dbms_output.put_line('Nume produs: '|| v_tab(i));
- 55 -
Baze de date. Limbajul PL/SQL

end if;
end loop;
dbms_output.put_line('Total produse in tabela: '||
v_tab.count);
END;
/

Figura 31 –Execuţia blocului PL/SQL

2. Să se utilizeze o variabilă de tipul INDEX BY cu elemente de acelaşi tip cu


înregistrările din tabela Angajati -angajati %ROWTYPE pentru parcurgerea atributelor
angajaţilor cu id-ul între 130 şi 135.
DECLARE
--declararea tipului si a variabilei
type ang_table is table of angajati%rowtype index by
pls_integer;
v_tab ang_table;
BEGIN
--incarcarea in tabela:
for i in 130..135 loop
SELECT * into v_tab(i) from angajati where
id_angajat=i;
end loop;
--extragerea din tabela
for i in v_tab.first..v_tab.last loop
dbms_output.put_line('Angajatul: '|| v_tab(i).nume|| '
lucreaza in departamentul: '||v_tab(i).id_departament);
end loop;
dbms_output.put_line('Total angajati in tabela: '||
v_tab.count);

- 56 -
Baze de date. Limbajul PL/SQL

END;
/

Figura 32 –Execuţia blocului PL/SQL

EXERCIŢII PROPUSE

1. Afişaţi toţi angajaţii şi comenzile încheiate de fiecare dintre aceştia. Folosiţi un cursor
pentru a încărca numele angajaţilor şi un cursor parametrizat pentru încărcarea
comenzilor încheiate de aceştia.

2. Afişaţi informaţii despre primele 3 comenzi care au cea mai mare valoare.

3. Afişaţi informaţii despre primii 5 salariaţi angajaţi (se va realiza filtrarea în funcţie de
câmpul Data_Angajare).

- 57 -
Baze de date. Limbajul PL/SQL

CAPITOLUL VI.
TRATAREA EXCEPŢIILOR
O excepţie reprezintă un identificator PL/SQL asociat unei condiţii anormale apărute
în timpul execuţiei unui bloc. Invocarea unei excepţii are ca efect terminarea blocului şi
întreruperea execuţiei normale a programului. Pentru evitarea unor situaţii de întrerupere
anormală, excepţia poate fi captată si poate fi specificată o rutină de tratare a acesteia.
Există două cazuri distincte de apariţie a erorilor:
a. Erorile sunt generate la nivelul serverului Oracle si excepţia asociata ei este automat
invocată;
b. Excepţiile sunt definite de utilizator şi declanşată în mod explicit prin instrucţiunea
RAISE utilizată în cadrul blocului.
Dacă excepţia este declanşată în secţiunea executabilă a unui bloc atunci se caută în
cadrul secţiunii de tratare a excepţiilor o rutină de tratare asociată. Daca PL/SQL poate trata
excepţia, ea nu este propagată în blocul exterior sau în mediul apelant, caz în care se
consideră că execuţia blocului s-a desfăşurat cu succes. Dacă nu există o rutină pentru tratarea
ei, excepţia este propagată în mediul apelant, caz în care execuţia blocului se termină cu eşec.
Tipuri de excepţii
În funcţie de modul în care se declanşează dar şi de modul în care sunt tratate, există
trei tipuri de excepţii:

Tipul Mod de manipulare


Excepţii pre-definite asociate erorilor care apar cel Nu trebuie declarate, serverul Oracle le
mai frecvent în blocurile PL/SQL (de exemplu declanşează în mod automat, dar trebuie
NO_DATA_FOUND, TOO_MANY_ROWS, tratate în secţiunea EXCEPTION.
INVALID_CURSOR,
ZERO_DIVIDE)
Excepţii non-predefine recunoscute de Oracle dar Trebuie declarate în secţiunea
tratate de utilizator cu ajutorul codului de eroare declarativă. Serverul Oracle le
returnat (de exemplu ORA- 01400). declanşează în mod automat, dar trebuie
tratate în secţiunea EXCEPTION.
Excepţii definite de utilizator, asociate unor condiţii Trebuie declarate în secţiunea
specifice de prelucrare (de exemplu cazul în care declarativă, declanşate de către utilizator
valoarea stocului unui anumit produs este zero) şi tratate în secţiunea EXCEPTION.

Tratarea tuturor excepţiilor se realizează în secţiunea EXCEPTION a blocurilor


PL/SQL astfel:
EXCEPTION
WHEN exception1 [OR exception2 …] THEN
statement1 ;
statement2 ;

[WHEN exception3 [OR exception4 …] THEN
statement1 ;
statement2 ;
…]

- 58 -
Baze de date. Limbajul PL/SQL

[WHEN OTHERS THEN


statement1 ;
statement2 ;
…]

VI.1. TRATAREA EXCEPŢIILOR PREDEFINITE


Acestea nu trebuie declarate, fiind definite de către Oracle Server şi declanşate
implicit la apariţia anumitor evenimente. Lista completă a excepţiilor predefinite Oracle poate
fi consultată din PL/SQL Reference, capitolul 10, pagina 10-4 [ORAPL] sau în Anexa 2.
Câteva exemple de excepţii predefinite sunt prezentate mai jos:

Numele Numărul erorii Descriere


NO_DATA_FOUND ORA-01403 O instrucţiune SELECT care ar fi trebuit sa
întoarcă o singura linie nu a returnat nicio
linie.
TOO_MANY_ROWS ORA-01422 O instrucţiune SELECT care ar fi trebuit sa
întoarcă o singura linie a returnat mai multe
linii.
INVALID_CURSOR ORA-01001 Apariţia unei operaţii ilegale asupra unui
şi cursor (de exemplu încercarea de a deschide
CURSOR_ALREADY_OPEN un cursor deja deschis).

Exemple:
1. Să se afişeze angajatul cu codul 10. Să se trateze eroarea apărută în cazul în care
nu există niciun angajat cu acest cod.
Rezolvare: în cazul în care nu există nicio înregistrare returnată de cererea SELECT şi
nu se tratează excepţia apărută (am comentat textul cu roşu din codul sursă de mai jos pentru a
simula această situaţie), va apărea pe ecran un mesaj de eroare, aşa cum se observă din figura
următoare:

Figura 33 – Mesajul de eroare în cazul netratării excepţiei NO_DATA_FOUND

- 59 -
Baze de date. Limbajul PL/SQL

Mesajul conţine codul erorii (ORA- 01403), textul explicativ: no data found, linie în
cadrul codului sursă unde a apărut (ORA-06512: at line 4). Din tabelul prezentat în Anexa 2,
observăm că acest cod al erorii este deja asociat cu o excepţie definită de Oracle –
NO_DATA_FOUND. In acest caz putem să utilizăm denumirea în secţiunea EXCEPTION
astfel:

set serveroutput on
DECLARE
v_nume VARCHAR2(20);
BEGIN
SELECT nume INTO v_nume
FROM angajati
WHERE id_angajat=10;
dbms_output.put_line(v_nume);
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Nu exista angajatul cu acest
ID!');

END;
/

In acest caz blocul se execută, dar în punctul în care se încearcă popularea variabilei
v_nume pe baza interogării SELECT se va declanşa excepţia, se caută numele acesteia în
secţiunea EXCEPTION, fiind găsită se execută instrucţiunile precizate aici, astfel blocul va
afişa mesajul ‘Nu există angajatul cu acest ID!’:

Figura 34 –Tratarea excepţiei NO_DATA_FOUND

- 60 -
Baze de date. Limbajul PL/SQL

2. Să se afişeze salariul angajatului cu prenumele John. Să se trateze eroarea apărută


în cazul în care există mai mulţi angajaţi cu acelaşi nume (interogarea SQL din bloc întoarce
mai multe înregistrări).
În acest caz excepţia apărută este ORA – 01422 care corespunde, conform tabelului,
excepţiei TOO_MANY_ROWS.

Figura 35 –Mesajul de eroare în cazul excepţiei TOO_MANY_ROWS

Tratăm excepţia în secţiunea EXCEPTION şi blocul va afişa un mesaj corespunzător


pentru a atenţiona utilizatorul:
set serveroutput on
DECLARE
sal angajati.salariul%type;
BEGIN
select salariul into sal from angajati where
prenume='John';
DBMS_OUTPUT.PUT_LINE('John are salariul de: '||sal);
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Exista mai multi salariati cu
numele John! Utilizati un cursor pentru selectie!');
END;
/

- 61 -
Baze de date. Limbajul PL/SQL

Figura 36 –Tratarea excepţiei TOO_MANY_ROWS

3. Să se afişeze comanda încheiată de un anumit client al cărui nume este citit de la


tastatură. Să se trateze excepţiile apărute.
Rezolvare: în acest caz tratăm ambele excepţii: NO_DATA_FOUND, TOO_MANY_ROWS:
set serveroutput on;
declare
nr_com comenzi.nr_comanda%type;
v_nume clienti.nume_client%type;
v_id_client clienti.id_client%type;
begin
v_id_client:=& id_client;
-- returnăm numele angajatului
select nume_client into v_nume
from clienti
where id_client = v_id_client;
--returnam comanda clientului
select nr_comanda into nr_com
from comenzi
where id_client = v_id_client;
dbms_output.put_line('Clientul'||v_nume||'are comanda:');
dbms_output.put_line(nr_com);
exception
when no_data_found then
dbms_output.put_line('Nu exista informatii privind acest
client sau comanda!');
when too_many_rows then
dbms_output.put_line('Clientul are mai multe comenzi!');
end;
/

- 62 -
Baze de date. Limbajul PL/SQL

Figura 37 –Tratarea excepţiilor predefinte NO_DATA_FOUND şi TOO_MANY_ROWS

VI.2. TRATAREA EXCEPŢIILOR NON-PREDEFINITE


O eroare a serverului Oracle ce nu are asociata o excepţie predefinită se poate trata
asociindu-i un nume codului de eroare returnat sau folosind clauza WHEN OTHERS. In
PL/SQL, directiva EXCEPTION_INIT determină compilatorul să asocieze un nume de
excepţie unui număr (cod) de eroare standard a serverului Oracle. Aceasta permite referirea
erorii standard prin nume şi scrierea unei rutine de tratare a ei.
Tratarea acestor tipuri de erori se realizează in 3 paşi:
1) Declararea excepţiei - se face în zona DECLARE a blocului:
NUME_EXCEPTIE EXCEPTION;
2) Asocierea codului erorii cu excepţia declarată anterior - se realizează tot în zona
DECLARE prin utilizarea directivei de compilare EXCEPTION_INIT:
PRAGMA EXCEPTION_INIT (NUME_EXCEPTIE, COD_EROARE);
Unde COD_EROARE este codul erorii standard Oracle care poate fi observat din
mesajul de eroare apărut pe ecran (ORA - );
3) Tratarea excepţiei în zona EXCEPTION a blocului:
EXCEPTION
WHEN NUME_EXCEPTIE THEN ........ ;

Se pot utiliza 2 atribute pentru a gestiona erorile apărute:


SQLCODE – returnează codul numeric al erorii. Poate avea următoarele valori:
 0 – nu a apărut nicio excepţie;
 1 – este o excepţie definită de utilizator;
 +100 – excepţia NO_DATA_FOUND;
 un număr negativ – o eroare Oracle Server;
SQLERRM – returnează mesajul asociat erorii.

- 63 -
Baze de date. Limbajul PL/SQL

Aceste atribute pot fi încărcate în variabile şi adăugate într-o tabelă de erori pentru
vizualizare şi verificare ulterioară.

Exemplu:
Să se adauge în tabela DEPARTAMENTE un nou departament cu ID-ul 200, fără a
preciza denumirea acestuia.
Rezolvare: În acest caz, prin neprecizarea unei valori pentru coloana
denumire_departament pe care este activată restricţia de NOT NULL, va apărea eroarea cu
codul ORA-01400 prin care programatorul este avertizat de încălcarea acestei restricţii de
integritate. Codul şi textul erorii pot fi observate din mesajul apărut pe ecran (figura
următoare):

Figura 38 –Mesajul de eroare în cazul excepţiei non-predefinte ORA-01400

Observăm că în acest caz nu avem un nume pentru codul erorii apărute, excepţia fiind
non-predefinită. Pentru tratarea acesteia procedăm conform paşilor descrişi mai sus: asociem
în secţiunea DECLARE un nume codului apărut, iar în secţiunea EXCEPTION tratăm eroarea:

SET SERVEROUTPUT ON
DECLARE
-- se asociază un nume codului de eroare apărut
INSERT_EXCEPT EXCEPTION;
PRAGMA EXCEPTION_INIT(INSERT_EXCEPT, -01400);
BEGIN
insert into departamente (id_departament,
denumire_departament) values (200, NULL);
EXCEPTION
--se tratează eroarea prin numele său
WHEN insert_except THEN
DBMS_OUTPUT.PUT_LINE('Nu ati precizat informatii
suficiente pentru departament');

- 64 -
Baze de date. Limbajul PL/SQL

--se afişează mesajul erorii


DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

Blocul va afişa şi mesajul de eroare corespunzător prin variabila SQLERRM:

Figura 39 –Tratarea excepţiei non-predefinte ORA-01400

2. Să se şteargă toate înregistrările din tabela PRODUSE.


Rezolvare: Acest lucru va duce la apariţia erorii cu codul ORA–2292, reprezentând
încălcarea restricţiei referenţiale. Valorile SQLCODE şi SQLERRM vor fi adăugate în tabela
ERORI pe care o vom crea în prealabil. ATENTIE! Aceste variabile nu se pot utiliza direct
într-o comandă SQL, drept pentru care vor fi încărcate mai întâi in variabile PL/SQL şi apoi
utilizate în instrucţiuni SQL.
--crearea tabelei ERORI
CREATE TABLE erori
(utilizator VARCHAR2(40),
data DATE,
cod_eroare NUMBER(10),
mesaj_eroare VARCHAR2(255));

--blocul PL/SQL
DECLARE
cod NUMBER;
mesaj VARCHAR2(255);
del_exception EXCEPTION;
PRAGMA EXCEPTION_INIT(del_exception, -2292);
BEGIN
DELETE FROM produse;

- 65 -
Baze de date. Limbajul PL/SQL

EXCEPTION
WHEN del_exception THEN
dbms_output.put_line('Nu puteti sterge produsul');
dbms_output.put_line('Exista comenzi asignate lui');
cod:=SQLCODE;
mesaj:=SQLERRM;
INSERT INTO erori VALUES(USER, SYSDATE, cod, mesaj);
END;
/
SELECT * FROM erori;

Figura 40 –Tratarea excepţiei non-predefinte ORA-02292

VI.3. TRATAREA EXCEPŢIILOR DEFINITE DE UTILIZATOR


In PL/SQL se pot defini excepţii ale utilizatorului. Ele trebuie declarate în secţiunea
declarativă a blocului şi declanşate explicit prin instrucţiunea RAISE.
Tratarea excepţiilor definite de utilizator se poate face astfel:
1) Se declară excepţia în secţiunea declarativă:
NUME_EXCEPTIE EXCEPTION;
2) Prin instrucţiunea RAISE se declanşează în mod explicit, în cadrul secţiunii
executabile: RAISE NUME_EXCEPTIE;
3) Se tratează în rutina corespunzătoare din secţiunea de tratare a excepţiilor:
WHEN NUME_EXCEPTIE THEN......
Exemple:
1. Să se declanşeze o excepţie în cazul în care utilizatorul încearcă să execute blocul
PL/SQL după ora 18.
Rezolvare: In cadrul codului sursă verificăm ora execuţiei blocului, dacă aceasta
depăşeşte ora permisă declansăm excepţia definită de noi în secţiunea DECLARE:
DECLARE

- 66 -
Baze de date. Limbajul PL/SQL

e_ora_nepermisa EXCEPTION;
BEGIN
IF TO_NUMBER(TO_CHAR(SYSDATE, 'HH24'))>=18 THEN
RAISE e_ora_nepermisa;
END IF;
EXCEPTION
WHEN e_ora_nepermisa THEN
dbms_output.put_line('Este ora '||TO_CHAR(SYSDATE,
'HH24'));
dbms_output.put_line('Operatiune permisa doar '||' in
timpul programului');
END;
/

2. Să se modifice denumirea produsului cu id-ul 3. Dacă nu se produce nicio


actualizare (valoarea atributului SQL%ROWCOUNT este 0) sau dacă apare o altă eroare
(OTHERS) atunci să se declanşeze excepţii prin care să fie avertizat utilizatorul:
DECLARE
invalid_prod EXCEPTION;
BEGIN
-- incercam sa actualizam produsul cu codul 3
UPDATE produse
SET denumire_produs='Laptop ABC'
WHERE id_produs=3;
--vefificam executia comenzii UPDATE si declasam
exceptia
IF SQL%NOTFOUND THEN
RAISE invalid_prod;
END IF;
EXCEPTION
WHEN invalid_prod THEN
DBMS_OUTPUT.PUT_LINE('Nu exista produsul cu acest ID');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('A aparut o eroare! Nu se poate
actualiza denumirea produsului!');
END;
/

- 67 -
Baze de date. Limbajul PL/SQL

Figura 41 –Tratarea excepţiilor definite de utilizator

Erorile definite de utilizator pot fi tratate la nivelul aplicaţiilor ca şi erorile Oracle


Server prin atribuirea de coduri cu ajutorul funcţiei:
RAISE_APPLICATION_ERROR (NR_EROARE, MESAJ);
unde NR_EROARE poate fi un număr negativ cuprins între -20000 si -20999, iar MESAJ este
textul explicativ al erorii.
In acest caz tratarea se realizează asemănător cu erorile non-predefinite Oracle Server.
Exemplu:
Să se atribuie excepţiei din exemplul anterior un cod şi un mesaj de eroare şi să se
adauge aceste valori în tabela ERORI.
DECLARE
cod NUMBER;
mesaj VARCHAR2(255);
invalid_prod EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_prod,-20999);
BEGIN
UPDATE produse
SET denumire_produs='Laptop ABC'
WHERE id_produs=3;
IF SQL%NOTFOUND THEN
--declansam exceptia
RAISE_APPLICATION_ERROR (-20999,'Cod produs invalid!');
END IF;
EXCEPTION
WHEN invalid_prod THEN
DBMS_OUTPUT.PUT_LINE('Nu exista produsul cu acest ID');
-- preluam codul si textul erorii
cod:=SQLCODE;
mesaj:=SQLERRM;
INSERT INTO ERORI VALUES(USER, SYSDATE, cod, mesaj);

- 68 -
Baze de date. Limbajul PL/SQL

END;
/
SELECT * FROM ERORI;

Figura 42 –Tratarea excepţiilor definite de utilizator

Propagarea excepţiilor
Odată excepţia declanşată în secţiunea executabilă a unui bloc, se caută în cadrul
secţiunii de tratare a excepţiilor (EXCEPTION) o rutină de tratare asociată. Dacă PL/SQL
poate trata excepţia, ea nu este propagată în blocul exterior sau în mediul apelant, caz în care
se consideră că execuţia blocului s-a desfăşurat cu succes.
Atunci când un sub-bloc tratează o excepţie, acesta se termină normal iar execuţia se
reia în blocul ce-l cuprinde imediat după instrucţiunea END a sub-blocului.
Dacă apare o excepţie, iar în blocul curent nu există o rutină pentru tratarea sa,
execuţia blocului se termina cu eşec, iar excepţia se propagă succesiv în blocurile exterioare
până este găsită într-unul din ele o rutină pentru tratarea ei. Dacă nu se găseşte nici una, în
mediul apelant apare o situaţie de excepţie nerezolvată, utilizator putând observa mesajul de
eroare care a întrerupt execuţia normală.

EXEMPLU
Utilizând blocuri imbricate, să se afişeze comanda încheiată de un anumit client al
cărui nume este citit de la tastatură. Să se trateze excepţiile apărute.
set serveroutput on;
declare
nr_com comenzi.nr_comanda%type;
v_nume clienti.nume_client%type;
v_id_client clienti.id_client%type;
begin

- 69 -
Baze de date. Limbajul PL/SQL

v_id_client:=& id_client;

-- returnăm numele clientului intr-un sub-bloc, iar


excepţia este tratată local

begin
select nume_client into v_nume
from clienti
where id_client = v_id_client;
dbms_output.put_line('Clientul: '||v_id_client);
exception
when no_data_found then
dbms_output.put_line('Nu exista informatii privind acest
client!');
end;

-- returnam comanda clientului intr-un sub-bloc, iar


exceptiile sunt propagate in blocul exterior

begin
select nr_comanda into nr_com
from comenzi
where id_client = v_id_client;
dbms_output.put_line('Clientul'||v_nume||'are comanda:');
dbms_output.put_line(nr_com);
end;

exception
when no_data_found then
dbms_output.put_line('Nu exista informatii privind
comenzile incheiate de acest client!');
when too_many_rows then
dbms_output.put_line('Clientul are mai multe comenzi!');
end;
/

EXERCIŢIU PROPUS

Creaţi o tabelă numită Mesaje, având un câmp unic, de tip Varchar2. Scrieţi un bloc PL/SQL
pentru a selecta codul comenzilor încheiate în anul 2000.
a. Dacă interogarea returnează mai mult de o valoare pentru numărul comenzii, trataţi
excepţia cu o rutină de tratare corespunzătoare şi inseraţi în tabela Mesaje mesajul “Atenţie!
In anul 2000 s-au încheiat mai multe comenzi!”.
b. Dacă interogarea nu returnează nicio valoare pentru numărul comenzii, trataţi
excepţia cu o rutină de tratare corespunzătoare şi inseraţi în tabela Mesaje mesajul “Atenţie!
In anul 2000 nu s-au încheiat comenzi!”.
c. Dacă se returnează o singura linie, introduceţi în tabela Mesaje numărul comenzii.
d. Trataţi orice altă excepţie cu o rutină de tratare corespunzătoare şi inseraţi în tabela
Mesaje mesajul “A apărut o altă eroare!”.

- 70 -
Baze de date. Limbajul PL/SQL

CAPITOLUL VII.
SUBPROGRAME PL/SQL

Subprogramele sunt blocuri PL/SQL care au un nume, o definiţie, o listă de parametri,


pot fi proceduri sau funcţii şi se pot stoca la nivelul bazei de date (proceduri/funcţii stocate)
sau la nivelul aplicaţiei (de exemplu procedurile sau funcţiile utilizate în aplicaţiile realizate în
Developer Suite).
Variabilele declarate în zona declarativă a subprogramelor se numesc parametri formali
(formal parameters), iar pentru aceştia se pot specifica valori implicite (DEFAULT).
Variabilele utilizate în apelul procedurii/funcţiei se numesc parametri actuali (actual
parameters).
In corpul procedurilor/funcţiilor nu se pot utiliza variabile globale sau de substituţie,
acestea vor fi transmise în subprograme cu ajutorul parametrilor.

VII.1. PROCEDURI
Parametrii utilizaţi în cadrul unei proceduri pot fi de mai multe tipuri:
 IN - parametru de intrare; valoarea sa se utilizează în cadrul procedurii fără a putea
fi modificată. Acesta reprezintă tipul implicit.
 OUT - parametru de ieşire; acesta primeşte valoare la ieşirea din cadrul procedurii,
valoare ce va putea fi utilizată în mediul apelant.
 IN OUT – parametru de intrare/ieşire; valoare sa este utilizată atât ca parametru de
intrare utilizat în procedură cât şi ca parametru de ieşire pentru transmiterea valorii
sale în mediul apelant.
Se impun următoarele concluzii în ceea ce priveşte modul de utililizare a parametrilor:
 un parametru IN poate apărea numai în partea dreapta a (:=);
 un parametru OUT poate apărea numai în partea stânga a (:=);
 un parametru IN OUT poate apărea în ambele părţi ale (:=).

Sintaxa pentru crearea unei proceduri:


CREATE [OR REPLACE] PROCEDURE NUME_PROC
[(param1 [IN|OUT|IN OUT] TIP1,
Param2[IN|OUT|IN OUT] TIP2, ....) ]
-- tipul variabilelor este precizat fără dimensiune (ex:
NUMBER sau VARCHAR2)
IS|AS
-- zona de declarare a variabilelor utilizate in
subprogram
-- NU se utilizeaza DECLARE
BEGIN
----
[EXCEPTION]
------
END [NUME_PROC];

- 71 -
Baze de date. Limbajul PL/SQL

Pentru a sterge procedura se utilizează următoarea sintaxă:


DROP PROCEDURE NUME_PROC;
Apelul unei proceduri se poate realiza în următoarele moduri:
 prin apelul dintr-un bloc PL/SQL anonim sau un alt subprogram;
 prin apel direct cu EXECUTE nume_proc sau EXEC nume_proc sau CALL
nume_proc;
 din alt mediu Oracle (Oracle Developer Suite).
Pentru afişarea erorilor apărute la compilare se utilizează SHOW ERRORS.

Exemplu: Să se realizeze o procedură pentru a adăuga în tabela PRODUSE o nouă


înregistrare cu id-ul produsului egal cu valorea ultimului id incrementat cu o unitate şi
denumire ‘Monitor LCD’. Să se apeleze procedura dintr-un bloc PL/SQL, dar şi direct.
SET SERVEROUTPUT ON
-- crearea procedurii. Nu avem parametri:
CREATE or REPLACE PROCEDURE adauga_produs
is
--declararea variabilelor locale
id_produs produse. id_produs %type;
denumire_produs produse. denumire_produs %type;
maxim produse. id_produs %type;
BEGIN
-- returnam ultimul id al produselor
select max(id_produs) into maxim from produse;
denumire_produs:='Monitor LCD';
-- adaugam o noua inregistrare:
insert into produse (id_produs, denumire_produs) values
(maxim+1, denumire_produs);
DBMS_OUTPUT.PUT_LINE ('S-au adaugat '||SQL%rowcount||'
inregistrari');
end;
/
SHOW ERRORS

- 72 -
Baze de date. Limbajul PL/SQL

Figura 43 – Crearea procedurii

-- apelul procedurii prin bloc PL/SQL anonim:


begin
adauga_produs;
end;
/
-- apelul direct:
call adauga_produs();

Figura 44 –Apelul procedurii într-un bloc PL/SQL

- 73 -
Baze de date. Limbajul PL/SQL

Procedura creată este stocată în baza de date şi poate fi apelată oricând, atât din blocuri
PL/SQL cât şi din alte medii de dezvoltare. Codul sursă şi lista parametrilor pot fi vizualizate
în SQL Developer din meniul din dreapta->lista Procedures:

Figura 45 – Vizualizarea codului sursă al procedurii create

Utilizarea parametrilor de tip IN – în acest caz valorile primite vor fi transferate şi


utilizate în cadrul procedurii fără a putea fi modificate.

Exemplu: Să se realizeze o procedură prin care să se majoreze salariul unui anumit


angajat al cărui id este primit ca parmetru cu un procent specificat tot printr-un parametru
de intrare.
Procedura MODIFICA_SALARIUL primeşte doi parametri: p_id_angajat şi procent
şi majorează cu procentul specificat salariul angajatului cu id_angajat=p_id_angajat:
CREATE OR REPLACE
PROCEDURE modifica_salariul_procent
(p_id_angajat IN angajati.id_angajat%type, procent IN
number)
IS
v_salariul angajati.salariul%type;
BEGIN
--returnam salariul angajatului cu id-ul specificat
Select salariul into v_salariul from angajati where
id_angajat=p_id_angajat;
dbms_output.put_line('Angajatul are salariul de
'||v_salariul);
--realizam majorarea salariului
Update angajati
Set salariul=salariul*(1+procent/100)
Where id_angajat=p_id_angajat;
- 74 -
Baze de date. Limbajul PL/SQL

-- returnam noul salariu


Select salariul into v_salariul from angajati where
id_angajat=p_id_angajat;
Dbms_output.put_line('Angajatul are acum salariul de
'||v_salariul);
END;
/
show errors;

Atenţie! Salariul angajatului nu va fi modificat definitiv până când tranzacţia nu este


finalizată prin instrucţiunea COMMIT care poate apărea în procedură (nerecomandat), în
blocul apelant sau în mediul de dezvoltare. Anularea tranzacţiei poate fi realizată în acelaşi
mod prin instrucţiunea ROLLBACK, aşa cum se observă în apelul următor:

EXECUTE modifica_salariul_procent(176, 10)


Rollback;

Figura 46 – Apelul procedurii MODIFICA_SALARIUL

Utilizarea parametrilor de tip OUT – în acest caz valorile din procedură vor fi
tranferate şi utilizate în afara procedurii.

Exemple:
1. Să se realizeze o procedură ce primeşte ca parametru de intrare id-ul unui angajat
şi returnează numele şi salariul său.
Procedura primeşte ca parametru de tip IN id-ul angajatului şi returnează prin
parametrii de tip OUT numele şi salariul acestuia:
CREATE OR REPLACE PROCEDURE cauta_angajat
(p_id_angajat IN angajati.id_angajat%type,

- 75 -
Baze de date. Limbajul PL/SQL

p_nume OUT angajati.nume%type,


p_salariul OUT angajati.salariul%type)
IS
BEGIN
--valorile parametrilor de tip OUT pot fi atribuite
direct în clauza SELECT
Select nume, salariul into p_nume, p_salariul from
angajati where id_angajat=p_id_angajat;
END;
/
Apelul procedurii se va face într-un bloc anonim care va afişa numele şi salariul
returnat de aceasta:
SET SERVEROUTPUT ON
DECLARE
v_nume angajati.nume%type;
v_salariul angajati.salariul%type;
BEGIN
Cauta_angajat(150, v_nume, v_salariul);
-- afisam valoarea parametrilor actuali:
DBMS_OUTPUT.PUT_LINE(' Angajatul '||v_nume||' are
salariul de: '||v_salariul);
END;
/

Figura 47 – Apelul procedurii CAUTA_ANGAJAT

2. Să se realizeze o procedură prin care se calculează salariul mediu al angajaţilor:


Procedura calculează salariul mediu şi îl returnează printr-un parametru de tip OUT:

CREATE or REPLACE PROCEDURE sal_mediu


(p_sal_mediu OUT number)
- 76 -
Baze de date. Limbajul PL/SQL

IS
BEGIN
Select AVG(salariul) into p_sal_mediu from angajati;
END;
/
show errors;

Apelul îl realizăm prin EXECUTE, direct în mediul apelant prin utilizarea unei
variabile de gazdă (de mediu):
VARIABLE v_sal_mediu NUMBER
EXECUTE sal_mediu(:v_sal_mediu)
Print v_sal_mediu

Figura 48 – Apelul procedurii SAL_MEDIU

3. Să se realizeze o procedură care primeşte ca parametru de intrare id-ul unui


produs şi returnează denumirea şi preţul minim cu care s-a vândut acest produs.
Procedura primeşte ca parametru de tip IN id-ul produsului şi returnează prin
parametrii de tip OUT denumirea şi preţul minim al acestuia:
CREATE or REPLACE PROCEDURE interogare_produse
(p_codp IN produse.id_produs%type,
p_denp OUT produse.denumire_produs%type,
p_pret_min OUT produse.pret_min%type)
IS
BEGIN
Select p.denumire_produs, MIN(r.pret) into p_denp,
p_pret_min
from produse p, rand_comenzi r
where r.id_produs=p.id_produs and p.id_produs=p_codp

- 77 -
Baze de date. Limbajul PL/SQL

group by p.denumire_produs;
END;
/
show errors;

Apelul procedurii într-un bloc anonim pentru un id introdus de la tastatură:


SET SERVEROUTPUT ON
DECLARE
v_denp produse.denumire_produs%type;
v_pret_min produse.pret_min%type;
BEGIN
interogare_produse(&cod_produs, v_denp, v_pret_min);
DBMS_OUTPUT.PUT_LINE('Produsul '||v_denp||' are pretul
minim: '||v_pret_min);
END;
/

Figura 49 – Apelul procedurii INTEROGARE_PRODUSE pentru id_produs=2245

Utilizarea parametrilor de tip IN OUT – în acest caz valorile vor fi transferate şi


utilizate în cadrul procedurii şi în afara acesteia.

Exemplu: Să se realizeze o procedură prin care se modifică salariul unui angajat


doar în cazul în care este mai mic decât media prin apelarea procedurii create mai sus.
Procedura MODIFICA_SALARIUL_MEDIU primeşte id-ul angajatului ca parametru
de intrare şi salariul mediu actual şi returnează prin parametrul de tip IN OUT salariul mediu
modificat prin apelul procedurii SAL_MEDIU.
CREATE or REPLACE PROCEDURE modifica_salariul_mediu
(p_id_angajat IN angajati.id_angajat%type, p_sal_mediu
IN OUT number)
IS
- 78 -
Baze de date. Limbajul PL/SQL

nume angajati.nume%type;
sal angajati.salariul%type;
BEGIN
Select nume, salariul into nume, sal from angajati
where id_angajat= p_id_angajat;
IF sal<p_sal_mediu then
--se apelează procedura pentru modificarea salariului
cu 15%
MODIFICA_SALARIUL (p_id_angajat, 15);
END IF;
-- se apelează procedura pentru recalcularea noului
salariu mediu
SAL_MEDIU (p_sal_mediu);
End;
/
Show errors

Apelul procedurii într-un bloc anonim se va realiza atât cu un id existent, caz în care
se vor afişa pe rând salariul mediu iniţial, salariul angajatului, salariul mediu actualizat şi apoi
cu un id inexistent care va declanşa excepţia NO_DATA_FOUND tratată în cadrul blocului
apelant:
SET SERVEROUTPUT ON
DECLARE
v_id_angajat angajati.id_angajat%type;
v_nume angajati.nume%type;
v_salariul angajati.salariul%type;
v_sal_mediu number;
BEGIN
--apelul cu id valid =105
v_id_angajat:=105;

--apelul procedurii pentru vizualizarea datelor


angajatului
CAUTA_ANGAJAT(v_id_angajat, v_nume, v_salariul);

--apelul procedurii pentru aflarea salariului mediu


initial
SAL_MEDIU (v_sal_mediu);
DBMS_OUTPUT.PUT_LINE('Salariul mediu initial este:
'||round(v_sal_mediu,2));

--apelul procedurii pentru modificarea salariului


angajatului
MODIFICA_SALARIUL_MEDIU (v_id_angajat, v_sal_mediu);
SAL_MEDIU (v_sal_mediu);
DBMS_OUTPUT.PUT_LINE('Salariul mediu actualizat este:
'||round(v_sal_mediu,2));
CAUTA_ANGAJAT(v_id_angajat, v_nume, v_salariul);

--apelul cu id invalid
v_id_angajat:=1230;
CAUTA_ANGAJAT(v_id_angajat, v_nume, v_salariul);

- 79 -
Baze de date. Limbajul PL/SQL

MODIFICA_SALARIUL_MEDIU (v_id_angajat, v_sal_mediu);


--tratarea exceptiei
Exception
When NO_DATA_FOUND then
DBMS_OUTPUT.PUT_LINE('Angajat inexistent! ID invalid');
END;
/
rollback;

Figura 50 – Apelul procedurii MODIFICA_SALARIUL_MEDIU

Stergerea unei proceduri se realizeaza prin comanda:


DROP PROCEDURE nume_procedura;

Vizualizarea procedurilor in dicţionarul metadatelor se realizează prin interogarea


tabelei virtuale USER_OBJECTS:
Exemplu: Să se vizualizeze toate procedurile stocate în schema utilizatorului curent:

- 80 -
Baze de date. Limbajul PL/SQL

Figura 51 – Vizualizarea procedurilor stocate din schema curentă

Pentru a vizualiza corpul unei anumite proceduri (de exemplu SAL_MEDIU) utilizăm
tabela virtuală USER_SOURCE:
Select text
From user_source
Where name='SAL_MEDIU' and type='PROCEDURE';

Figura 52 – Vizualizarea codului sursă al procedurii SAL_MEDIU

- 81 -
Baze de date. Limbajul PL/SQL

VII.2. FUNCŢII

Spre deosebire de proceduri, la crearea unei funcţii trebuie precizat obligatoriu tipul
returnat de aceasta. O altă caracteritică a funcţiilor este aceea că ele se pot apela atât dintr-un
mediu cu nucleu PL/SQL cât şi direct din SQL, în mod asemănător cu funcţiile predefinite, ca
spre exemplu SUM, AVG, ROUND, TO_DATE etc.
Sintaxa pentru crearea unei functii:
CREATE [OR REPLACE] FUNCTION nume_functie
[(param1 [IN] TIP1,Param2[IN] TIP2, ....) ]
RETURN TIP_DATA
-- tipul variabilelor este precizat fara dimensiune (ex:
NUMBER sau VARCHAR2)
IS|AS
-- zona de declarare a variabilelor utilizate in
subprogram
-- nu se utilizeaza DECLARE
BEGIN
----
RETURN VALOARE;
[EXCEPTION]
------
END [NUME_FUNCTIE];

Pentru vizualizarea tipului returnat se utilizează comanda DESCRIBE:


DESCRIBE nume_functie;
Pentru a sterge funcţia din schema dicţionarul bazei de date se utilizează comanda
DROP:
DROP FUNCTION nume_functie;

Exemple:
1. Să se creeze o funcţie care returnează true, respectiv false dacă un angajat al cărui
ID este primit ca parametru are salariul mai mare, respectiv mai mic sau egal cu salariul
mediu şi null în cazul în care angajatul nu există.
CREATE OR REPLACE FUNCTION verifica_salariul
(p_id_angajat IN angajati.id_angajat%type, p_sal_mediu
IN number)
RETURN Boolean
IS
v_salariul angajati.salariul%type;
BEGIN
SELECT salariul into v_salariul from angajati where
id_angajat=p_id_angajat;
IF v_salariul > p_sal_mediu then
return true;
ELSE
return false;
end if;
EXCEPTION
WHEN no_data_found THEN

- 82 -
Baze de date. Limbajul PL/SQL

return NULL;
end;
/
show errors

Figura 53 – Crearea funcţiei VERIFICA_SALARIUL

Vizualizarea tipului returnat se poate face cu comanda:


describe verifica_salariul;
iar codul sursă al acesteia se poate vizualiza astfel:
Select text
From user_source
Where name='VERIFICA_SALARIUL' and type='FUNCTION';

Acesta se poate vizualiza şi edita din meniul dreapta-> Functions.

- 83 -
Baze de date. Limbajul PL/SQL

Figura 54 – Vizualizarea codului sursă al funcţiei VERIFICA_SALARIUL

Apelul funcţiei se poate face dintr-un bloc PL/SQL. Vom apela mai întâi procedura
SAL_MEDIU pentru a returna salariul mediu, apoi vom apela funcţia creată cu 3 valori
pentru ID_angajat pentru a verifica execuţia sa:
SET SERVEROUTPUT ON
DECLARE
v_sal_mediu number;
BEGIN
--apelul procedurii pentru calculul salariului mediu:
SAL_MEDIU (v_sal_mediu);
--primul apel al functiei

IF (verifica_salariul(11, v_sal_mediu) IS NULL) then


dbms_output.put_line('Angajat cu ID invalid!');
elsif (verifica_salariul(11, v_sal_mediu)) then
dbms_output.put_line('Salariatul are salariul mai mare
decat media!');
else
dbms_output.put_line(' Salariatul are salariul mai mic
decat media!');
end if;

--al doilea apel


IF (verifica_salariul(110, v_sal_mediu) IS NULL) then
dbms_output.put_line('Angajat cu ID invalid!');
elsif (verifica_salariul(110, v_sal_mediu)) then
dbms_output.put_line('Salariatul are salariul mai mare
decat media!');
else

- 84 -
Baze de date. Limbajul PL/SQL

dbms_output.put_line(' Salariatul are salariul mai mic


decat media!');
end if;

--al treilea apel


IF (verifica_salariul(104, v_sal_mediu) IS NULL) then
dbms_output.put_line('Angajat cu ID invalid!');
elsif (verifica_salariul(104, v_sal_mediu)) then
dbms_output.put_line('Salariatul are salariul mai mare
decat media!');
else
dbms_output.put_line(' Salariatul are salariul mai mic
decat media!');
end if;
END;
/

Figura 55 – Apelul funcţiei VERIFICA_SALARIUL

2. Să se realizeze o funcţie pentru verificarea şi validarea codului numeric personal


(CNP):
CREATE OR REPLACE FUNCTION CC_CNP (pcnp varchar2)
RETURN VARCHAR2
IS
ponderi VARCHAR2(12) := '279146358279';
i NUMBER(2);
s NUMBER(4);
mod_11 NUMBER(2);
cc VARCHAR2(1);
mesaj VARCHAR2(200);
- 85 -
Baze de date. Limbajul PL/SQL

BEGIN
s := 0;
mesaj:= null;
IF length(pcnp) != 13 THEN
mesaj := 'CNP nu are 13 caractere';
ELSE
BEGIN
FOR i IN 1..12 LOOP
BEGIN
IF substr(pcnp,i,1) IN
('0','1','2','3','4','5','6','7','8','9') THEN
s := s + to_number(substr(ponderi,i,1)) *
to_number(substr(pcnp,i,1));
ELSE
mesaj := 'CNP ne numeric';
RETURN mesaj;
END IF;
END;
END LOOP;
mod_11 := mod(s,11);
IF mod_11 = 10 THEN cc := '1';
ELSE
cc := to_char(mod_11);
END IF;
IF cc = substr(pcnp,13,1) then
mesaj := ‘CNP Corect’;
ELSE
mesaj := 'Cifra de control eronata(CNP eronat)';
END IF;
END;
END IF;
RETURN mesaj;
END cc_cnp;
/

Utilizarea funcţiilor PL/SQL în SQL – se realizează asemănător cu apelul funcţiilor


predefinite Oracle.
Exemplu:
Să se creeze o funcţie PL/SQL pentru calculul taxelor.
Funcţia va primi drept parametri valoarea la care se aplică taxa şi procentul taxei
respective.
CREATE OR REPLACE FUNCTION taxa (value IN NUMBER, proc
IN NUMBER)
RETURN NUMBER IS
BEGIN
RETURN (value*proc);
END taxa;
/
show errors

Apelul funcţiei se va realiza într-o interogare care afişează id_ul unui produs,
cantitatea, preţul şi TVA-ul acestuia.

- 86 -
Baze de date. Limbajul PL/SQL

SELECT id_produs, cantitate, pret, taxa (pret, 0.19) as


tva
FROM rand_comenzi;

Figura 56 – Apelul funcţiei TAXA într-o interogare

Aceste funcţii pot fi utilizate in toate clauzele SELECT sau în comenzi de manipulare
a datelor, spre exemplu:
SELECT id_produs, cantitate, pret, taxa (pret, 0.19)
FROM rand_comenzi
WHERE taxa(pret, 0.19)>(select avg(taxa(pret, 0.19))
from rand_comenzi)
ORDER BY taxa(pret, 0.19) DESC;

Observaţii!
1. Funcţiile utilizate în expresii SQL trebuie să accepte doar parametri de tip IN şi să
returneze numai tipuri de date specifice SQL.
2. Funcţiile apelate în cadrul expresiilor SQL pot fi conţinute în pachete de
subprograme.
3. Funcţiile nu trebuie să conţină comenzi DML (update, delete, insert) sau comenzi
DDL sau pentru controlul tranzacţiilor (commit, rollback) şi nici nu trebuie sa apeleze alte
subprograme care sa încalce aceste restricţii. În cazul în care aceste condiţii nu sunt respectate
se va genera o eroare, aşa cum se poate observa din exemplul următor:

Exemplu: Apelul urmatoarei funcţii dintr-o instrucţiune SQL va returna eroarea:


ORA-04091: table [nume_tabela] is mutating, trigger/function may not see it din cauza
faptului că în cadrul funcţiei se deschide o tranzacţie nefinalizată:
CREATE OR REPLACE FUNCTION produs_nou (pret_min NUMBER)
RETURN NUMBER
IS

- 87 -
Baze de date. Limbajul PL/SQL

BEGIN
INSERT INTO produse (id_produs, denumire_produs,
pret_min)values (1, 'cafea', pret_min);
RETURN (pret_min+100);
END;
/
In ciuda faptului că funcţia se va crea fără probleme, putând fi apelată într-un bloc
PL/SQL, apelul său în instructiuni SQL va genera eroare precizată:
UPDATE produse
SET pret_min= produs_nou(2000)
WHERE id_produs=2245;

Figura 57 – Apelul funcţiei PRODUS_NOU va genera o eroare

Ştergerea unei funcţii se realizează utilizând comanda:


DROP FUNCTION nume_functie;

Vizualizarea tuturor funcţiilor din dictionarul metadatelor pentru utilizatorul curent se


realizează prin comanda:
Select object_name
From user_objects
Where object_type='FUNCTION';

Pentru a vizualiza codul sursă al funcţiei, de exemplu pentru funcţia TAXA:


Select text
From user_source
Where name='TAXA' and type='FUNCTION'
ORDER BY line;

- 88 -
Baze de date. Limbajul PL/SQL

EXERCIŢII PROPUSE

1. Creaţi o funcţie care primească drept parametru codul angajatului şi care să


returneze numărul de funcţii distincte (id_functie) pe care acesta le-a avut de-a
lungul timpului. Testaţi funcţia într-un bloc anonim.

2. Parcurgeţi printr-un cursor toţi angajaţii departamentului IT şi pentru fiecare


dintre aceştia specificaţi numărul de funcţii distincte deţinute în cadrul
organizaţiei (prin apelul funcţiei create la exerciţiul 1).

3. Transformaţi funcţia creată la exerciţiul 1 într-o procedură cu parametri de tip


IN, respectiv OUT. Apelaţi procedura într-un bloc anonim.

- 89 -
Baze de date. Limbajul PL/SQL

CAPITOLUL VIII.
PACHETE DE SUBPROGRAME
Pachetele de subprograme grupează variabile, funcţii, proceduri, tipuri de date
PL/SQL care, de obicei, sunt corelate logic. Pachetele sunt formate din 2 secţiuni: specificaţia
pachetului care este o zonă publică în care sunt definiţiile subprogramelor puclice şi
declaraţii de variabile şi excepţii publice şi corpul pachetului care este o zonă privată în care
este implementat codul sursă al tuturor subprogramelor. In această zonă pot apărea şi
proceduri sau funcţii private, utilizate pentru diferite prelucrări interne ale pachetului.

Specificaţia pachetului se realizează utilizând următoarea sintaxă:


CREATE [OR REPLACE] PACKAGE nume_pachet
IS|AS
--declaratii de variabile si tipuri publice
--specificatii ale subprogramelor publice
END [nume_pachet];

Corpul pachetului se realizează utilizând următoarea sintaxă:


CREATE [OR REPLACE] PACKAGE BODY nume_pachet
IS|AS
--declaratii de variabile si tipuri private
--codul sursa al subprogramelor publice si private
BEGIN
--Este optional si se executa o singura data la primul
apel
END [nume_pachet];

Observaţie: In cadrul pachetelor pentru a utiliza o funcţie/procedură în cadrul unui alt


subprogram din pachet, aceasta trebuie declarată înainte de utilizare (principiul forward
declarations).

Exemple:
1. Să se realizeze un pachet care să conţină o funcţie pentru căutarea unui client, o
funcţie pentru căutarea unui produs, o procedură pentru introducerea unei comenzi noi, o
procedură de actualizare a unei comenzi şi o funcţie pentru ştergerea unei comenzi. Toate
subprogramele vor fi publice.
create or replace PACKAGE gestiune_comenzi
IS
function cauta_client
(p_nume_client clienti.nume_client%type)
return number;

function cauta_produs
(p_denumire_produs produse.denumire_produs%type)
return number;

- 90 -
Baze de date. Limbajul PL/SQL

procedure introduce_comanda
(p_nr_comanda comenzi.nr_comanda%type,
p_id_client comenzi.id_client%type,
p_id_produs rand_comenzi.id_produs%type,
p_pret rand_comenzi.pret%type,
p_cantitate rand_comenzi.cantitate%type);

procedure actualizeaza_comanda
(p_nr_comanda comenzi.nr_comanda%type,
p_id_produs rand_comenzi.id_produs%type,
p_cantitate rand_comenzi.cantitate%type);

function sterge_comanda
(p_nr_comanda comenzi.nr_comanda%type)
return boolean;

comanda_inexistenta exception;
END;
/
Show errors

Figura 58 – Crearea specificaţiei pachetului GESTIUNE_COMENZI


Pachetul este creat şi stocat în baza de date, acest lucru poate fi vizualizat din meniul
din stânga ->Packages.
Corpul pachetului se va crea separat şi va conţine implementarea procedurilor şi
funcţiilor declarate în specificaţie:

create or replace
PACKAGE BODY gestiune_comenzi IS

- 91 -
Baze de date. Limbajul PL/SQL

/* functia cauta_client primeste ca parametru numele


clientului si returneaza id-ul acestuia sau null in
cazul in care nu exista clientul cautat sau sunt mai
multi clienti cu acelasi nume */

function cauta_client
(p_nume_client clienti.nume_client%type)
return number
is
v_id_client clienti.id_client%type;
begin
select id_client into v_id_client from clienti
where nume_client =p_nume_client;
return v_id_client;
exception
when no_data_found then
return null;
when too_many_rows then
return null;
end cauta_client;

/* functia cauta_produs primeste ca parametru denumirea


produsului si returneaza id-ul acestuia sau null in
cazul in care nu exista produsul cautat sau sunt mai
multe produse cu aceeasi denumire */

function cauta_produs
(p_denumire_produs produse.denumire_produs%type)
return number
is
v_id_produs produse.id_produs%type;
begin
select id_produs into v_id_produs from produse
where denumire_produs like p_denumire_produs||'%';
return v_id_produs;
exception
when no_data_found then
return null;
when too_many_rows then
return null;
end cauta_produs;

/* procedura introduce_comanda adauga o comanda noua de


tip online in tabela comenzi si tdetaliile acesteia –
cantitatea si pretul in tabela rand_comenzi */

procedure introduce_comanda
(p_nr_comanda comenzi.nr_comanda%type,
p_id_client comenzi.id_client%type,
p_id_produs rand_comenzi.id_produs%type,
p_pret rand_comenzi.pret%type,
p_cantitate rand_comenzi.cantitate%type)

- 92 -
Baze de date. Limbajul PL/SQL

is
begin
insert into comenzi (nr_comanda, id_client, data,
modalitate) values (p_nr_comanda, p_id_client, sysdate,
'online');
insert into rand_comenzi (nr_comanda, id_produs, pret,
cantitate) values (p_nr_comanda, p_id_produs, p_pret,
p_cantitate);
exception
when others then
raise comanda_inexistenta;
end introduce_comanda;

/* procedura actualizeaza detaliile unei comenzi:


cantitatea sau pretul*/
procedure actualizeaza_comanda
(p_nr_comanda comenzi.nr_comanda%type,
p_id_produs rand_comenzi.id_produs%type,
p_cantitate rand_comenzi.cantitate%type)
is
begin
update rand_comenzi
set cantitate = p_cantitate
where id_produs=p_id_produs and
nr_comanda=p_nr_comanda;
if sql%notfound then
raise comanda_inexistenta;
end if;
end actualizeaza_comanda;

/* functia realizeaza stergerea unei comenzi din


tabelele comenzi si din rand_comenzi si returneaza true
daca au fost sterse si false in caz contrar */
function sterge_comanda
(p_nr_comanda comenzi.nr_comanda%type)
return boolean
is
begin
delete from rand_comenzi
where nr_comanda = p_nr_comanda;
delete from comenzi
where nr_comanda = p_nr_comanda;
if sql%found then
return true;
else
return false;
raise comanda_inexistenta;
end if;
end sterge_comanda;

BEGIN
dbms_output.put_line('Se executa pachetul!');

- 93 -
Baze de date. Limbajul PL/SQL

EXCEPTION
when comanda_inexistenta then
dbms_output.put_line ('Nu se poate procesa comanda cu
acest numar! ');
end gestiune_comenzi;
/
Show errors

Figura 59 – Crearea corpului pachetului GESTIUNE_COMENZI

Corpul pachetului se va stoca în baza de date, acest lucru putând fi vizualizat din
meniul din stânga->Packages: sub denumirea pachetului GESTIUNE_COMENZI apare şi
corpul acestuia.
Apelul procedurilor / funcţiilor din cadrul pachetului se poate face în mod asemănător
cu cel al subprogramelor, cu menţiunea că numele acestora va fi prefixat cu numele
pachetului din care fac parte:
declare
v_id_client number;
v_id_produs number;
begin
v_id_client:=gestiune_comenzi.CAUTA_CLIENT(initcap('&num
e_client'));
if v_id_client is null then
dbms_output.put_line ('Nu exista acest client!');
else
v_id_produs:=gestiune_comenzi.cauta_produs('&denumire');
if v_id_produs is null then
dbms_output.put_line ('Nu exista acest produs!');
- 94 -
Baze de date. Limbajul PL/SQL

else
gestiune_comenzi.introduce_comanda(&nr_comanda,
v_id_client, v_id_produs, &pret, &cantitate);
end if;
end if;
end;
/

Figura 60 – Apelul subprogramelor din cadrul pachetului GESTIUNE_COMENZI

Ştergerea pachetului se realizează cu comenzile:


DROP PACKAGE nume_pachet;
DROP PACKAGE BODY nume_pachet;

Vizualizarea specificaţiilor pachetelor in dicţionarul metadatelor poate face din meniul


stânga->Packages sau direct prin comanda:
Select text
From user_source
Where name='GESTIUNE_COMENZI' and type='PACKAGE'

- 95 -
Baze de date. Limbajul PL/SQL

Figura 61 – Vizualizarea codului sursă al specificaţiei pachetului GESTIUNE_COMENZI

Pentru a vizualiza corpul pachetului se utilizează comanda:


Select text
From user_source
Where name='GESTIUNE_COMENZI' and type='PACKAGE BODY'

2. Să se creeze un pachet numit GESTIUNE_ANGAJATI care să conţină o funcţie


numită nr_comenzi, o funcţie creştere_sal şi o procedură numită modificare_sal. Scopul
pachetul e de a modifica salariul tuturor angajaţilor care au realizat comenzi. Regula de
modificare a salariului e următoarea:
 Între 1 şi 3 de comenzi o majorare de 5%
 Între 3 şi 8 de comenzi o majorare de 10%
 Între 8 şi 20 de comenzi o majorare de 15%
 Peste 20 de comenzi o majorare de 20%
Funcţia nr_cerere va număra comenzile fiecărui angajat, funcţia creştere va
determina suma cu care va creşte salariul fiecărui angajat în parte după regula prezentată,
iar procedura modif_sal va realiza modificarea efectivă a salariului.
Specificaţia pachetului este:
create or replace
PACKAGE gestiune_angajati is
function nr_comenzi(id_a angajati.id_angajat%type)
return number;
function creste_salariul(nr_c number, id_a
angajati.id_angajat%type)
return angajati.salariul%type;
procedure modifica_salariul(id_a in
angajati.id_angajat%type,pr in angajati.salariul%type);
end;

- 96 -
Baze de date. Limbajul PL/SQL

/
Show errors

Corpul pachetului:
create or replace
package body gestiune_angajati is

function creste_salariul (nr_c number, id_a


angajati.id_angajat%type)
return angajati.salariul%type
is
var angajati.salariul%type;
sal angajati.salariul%type;
begin
select salariul into sal
from angajati
where id_angajat= id_a;
sal:=sal* (case
when nr_c between 1 and 3 then 0.05
when nr_c between 3 and 8 then 0.1
when nr_c between 8 and 20 then 0.15
else 0.2
end);
return sal;
end creste_salariul;

function nr_comenzi (id_a angajati.id_angajat% TYPE)


return number is v_nr NUMBER;
begin
select count(nr_comanda) into v_nr
from comenzi
where id_angajat=id_a;
return v_nr;
end nr_comenzi;

procedure modifica_salariul (id_a in


angajati.id_angajat%type,pr in angajati.salariul%type)
is sal angajati.salariul%type;
begin
select salariul into sal
from angajati
where id_angajat=id_a;
update angajati set salariul=sal+pr
where id_angajat=id_a;
end modifica_salariul;

end gestiune_angajati;
/
Show errors

- 97 -
Baze de date. Limbajul PL/SQL

Figura 62 – Crearea pachetului GESTIUNE_ANGAJATI

Apelul procedurilor şi funcţiilor din pachet:


set serveroutput on
declare
cursor c is select nume, salariul,id_angajat
from angajati;
sal_nou angajati.salariul%type;
nr_c number;
begin
for var in c loop
nr_c:= gestiune_angajati.nr_comenzi(var.id_angajat);
if nr_c>0 then
dbms_output.put_line('Angajatul: '|| var.nume||'
are: '|| nr_c||' comenzi!' );
sal_nou:= gestiune_angajati.creste_salariul
(nr_c,var.id_angajat);
dbms_output.put_line('Salariul sau va fi:
'||sal_nou);
gestiune_angajati.modifica_salariul
(var.id_angajat,sal_nou);
dbms_output.put_line('Salariul s-a actualizat');
end if;
exit when c%notfound;
end loop;
exception
when others then
dbms_output.put_line('Cursor utilizat incorect!');
end;
/
- 98 -
Baze de date. Limbajul PL/SQL

rollback;

Figura 63 – Apelul procedurilor din pachetul GESTIUNE_ANGAJATI

3. Să se realizeze un pachet care va conţine o procedură publică pentru calcularea


valorii bonusului standard care poate fi acordat noilor angajaţi şi o funcţie privată care va
verifica dacă noul bonus se situează între 0 şi valoarea maximă a comisionului existent în
tabela Angajaţi. Pachetul va conţine o variabilă publică prin care se va seta iniţial valoarea
bonusului standard de 3%.

CREATE OR REPLACE PACKAGE bonus_angajati


IS
--declarare de variabile publice si specificatii de
functii si proceduri publice
bonus_standard NUMBER;
--exceptii
bonus_invalid exception;
pragma exception_init(bonus_invalid, -20100);
PROCEDURE modifica_bonus (bonus_nou NUMBER);
END bonus_angajati;
/
show errors

Corpul pachetului:
CREATE OR REPLACE PACKAGE BODY bonus_angajati
IS
-- functie urmatoare este privata
FUNCTION validare_bonus (p_bonus NUMBER)
RETURN BOOLEAN
IS
- 99 -
Baze de date. Limbajul PL/SQL

v_max_bonus angajati.comision%type;
BEGIN
SELECT MAX(comision) INTO v_max_bonus
FROM angajati;
RETURN (p_bonus BETWEEN 0.0 AND v_max_bonus);
END validare_bonus;

-- corpul procedurii publice


PROCEDURE modifica_bonus (bonus_nou NUMBER)
IS
BEGIN
--utilizarea functiei private din cadrul pachetului si a
variabilei publice
IF validare_bonus (bonus_nou) THEN
bonus_standard:= bonus_nou;
ELSE
RAISE_APPLICATION_ERROR(-20100, 'Comision Invalid!');
END IF;
END modifica_bonus;
-- Corpul pachetului.
BEGIN
IF bonus_standard IS NULL THEN
bonus_standard:=0.03;
END IF;
EXCEPTION
WHEN bonus_invalid THEN
DBMS_OUTPUT.PUT_LINE('Comisionul este foarte mare!');
END bonus_angajati;
/
show errors

Apelul subprogramelor din interiorul pachetului:

set serveroutput on
--primul apel va declansa eroarea BONUS_INVALID
EXECUTE bonus_angajati.modifica_bonus(0.95);
-- al doilea apel va modifica valoarea comisionului la 15%
EXECUTE bonus_angajati.modifica_bonus (0.15);
BEGIN
DBMS_OUTPUT.PUT_LINE('Comisionul este: '||
bonus_angajati.bonus_standard);
END;
/

- 100 -
Baze de date. Limbajul PL/SQL

Figura 64 – Apelul procedurilor din pachetul BONUS_ANGAJATI

Supraîncărcarea subprogramelor se poate realiza numai pentru funcţii sau proceduri


din cadrul pachetelor, nu şi pentru subprograme singulare (stocate direct in baza de date).
Există restricţii în sensul că nu se pot supraîncărca 2 subprograme care au paramentrii de
tipuri asemănătoare (ex: NUMBER si DECIMAL sau VARCHAR2 si VARCHAR).

Exemplu: Să se realizeze un pachet care să conţină două proceduri adaugare_functie


cu parametri diferiţi; prima funcţie primeşte parametri valori pentru toate atributele tabelei
Functii, iar cea de a doua funcţie primeşte parametru doar pentru denumirea funcţiei nou
adăugate, urmând ca id-ul acesteia sa fie obtinut prin extragerea primelor 5 caractere din
denumire.
CREATE OR REPLACE PACKAGE gestiune_functii
IS
PROCEDURE adaugare_functie (p_id_functie varchar2,
p_denumire varchar2, p_sal_min number:=2000, p_sal_max
number :=10000);
PROCEDURE adaugare_functie (p_denumire varchar2);
END gestiune_functii;
/

create or replace
PACKAGE BODY gestiune_functii
IS
PROCEDURE adaugare_functie (p_id_functie varchar2,
p_denumire varchar2, p_sal_min number:=2000, p_sal_max
number :=10000)
IS
BEGIN

- 101 -
Baze de date. Limbajul PL/SQL

INSERT INTO functii values (p_id_functie, p_denumire,


p_sal_min, p_sal_max);
--DBMS_OUTPUT.PUT_LINE('S-a adaugat functia:
'||p_id_functie||' cu denumirea: '||p_denumire) ;
END adaugare_functie;

PROCEDURE adaugare_functie (p_denumire varchar2)


IS
v_id functii.id_functie%type;
BEGIN
v_id:=substr(p_denumire, 1,4);
--DBMS_OUTPUT.PUT_LINE('S-a adaugat functia: '||v_id||'
cu denumirea: '||p_denumire) ;
INSERT INTO functii (id_functie, denumire_functie) values
(v_id, p_denumire);
END adaugare_functie;
END gestiune_functii;
/
Show errors

Apelul procedurilor se va face în funcţie de numărul şi tipul parametrilor:


EXECUTE gestiune_functii. adaugare_functie (980,
'Education 980', 2500)
EXECUTE gestiune_functii. adaugare_functie ('Education
MAX', 2500)
SELECT * FROM FUNCTII;

Figura 65 – Apelul procedurilor din pachetul GESTIUNE_FUNCTII

- 102 -
Baze de date. Limbajul PL/SQL

Criptarea codului sursă al pachetelor şi subprogramelor


Pentru protejarea codului sursă al subprogramelor şi al pachetelor, acesta se poate
stoca criptat în dicţionarul datelor. Execuţia subprogramelor nu va fi afectată de procedura de
criptare, apelul lor desfăşurându-se normal. Pentru a re-crea un anumit subprogram sau pentru
a realiza o anumită modificare este necesar fişierul cu codul sursă necriptat care va fi salvat în
prealabil într-o locaţie sigură.
Paşii pentru a realiza acest lucru sunt următorii:
 se salvează codul sursă într-un fişier cu extensia .sql
 se utilizează utilitarul WRAP din linia de comandă a sistemului de operare.
 se execută fişierul rezultat în mediul SQL.
Sintaxa pentru comanda WRAP este următoarea:
WRAP INAME=fis_sursa ONAME=is_dest

Exemplu: Să se cripteze codul sursă al pachetului GESTIUNE_FUNCTII:


1. Salvăm codul sursă al pachetului în fişierul in_pachet.sql.

Figura 66 – Pas 1 – Salvarea codului sursă al pachetului GESTIUNE_FUNCTII

2. Deschidem o sesiune în linie de comandă şi criptăm fişierul sursă:


c:\> wrap iname=in_pachet.sql oname=out_pachet.plb

- 103 -
Baze de date. Limbajul PL/SQL

Figura 67 – Pas 2 – Criptarea codului sursă al pachetului GESTIUNE_FUNCTII

3. Rulăm fişierul criptat în SQL Developer pentru re-crearea pachetului

Figura 68 – Pas 3 – Rularea codului sursă criptat al pachetului GESTIUNE_FUNCTII

Putem vizualiza conţinutul pachetului, însă codul sursă va fi criptat:


Select text
From user_source
Where name='GESTIUNE_FUNCTII' and type='PACKAGE BODY';

- 104 -
Baze de date. Limbajul PL/SQL

Figura 69 –Codul sursă este stocat criptat în dicţionarul metadatelor

EXERCIŢII PROPUSE

1. Creaţi un pachet de subprograme care să conţină două proceduri publice şi o


funcţie privată.

2. Adăugaţi un nou subprogram în pachetul creat la exerciţiul 1, astfel încât să


exemplificaţi operaţia de supraîncărcare.

3. Criptaţi codul sursă al pachetului creat.

- 105 -
Baze de date. Limbajul PL/SQL

CAPITOLUL IX.
DECLANŞATORI

Declanşatorii (triggers) pe baza de date sunt asociaţi cu o tabelă, tabelă virtuală,


schemă sau chiar cu baza de date şi se execută la apariţia unui eveniment. Tipurile de
evenimente pot fi:
 operaţii de manipulare a datelor pe o tabelă (INSERT/ UPDATE/ DELETE);
 operaţii de manipulare a datelor pe o tabelă virtuală (VIEW) cu clauza INSTEAD
OF;
 operaţii de definire a datelor (CREATE, ALTER, DROP) la nivel de bază de date
sau schemă;
 evenimente la nivelul schemei sau bazei de date (SHUTDOWN sau
LOGON/OFF).

IX.1. DECLANŞATORI PENTRU OPERAŢIILE DE MANIPULARE A DATELOR


Declanşatorii se folosesc pentru gestionarea restricţiilor de integritate complexe,
monitorizarea şi controlul operaţiilor, însă nu se recomandă construirea în exces a acestora
deoarece se îngreunează execuţia operaţiilor.
Pentru fiecare declanşator se stabileşte:
 Evenimentul (comanda) care îl declanşează (INSERT| UPDATE| DELETE). Se
pot specifica mai multe evenimente pentru un singur declanşator.
 Momentul de timp la care se declanşează (BEFORE| AFTER| INSTEAD OF).
Pentru tabelele virtuale se utilizează clauza INSTEAD OF, iar acţiunile de
manipulare vor fi înlocuite de corpul declanşatorului şi vor fi afectate tabelele din
care este construită tabela virtuală.
 Nivelul de acţiune (ROW| STATEMENT) - dacă declanşatorul este la nivel de
rând se execută pentru fiecare înregistrare afectată de comenzile: INSERT|
UPDATE| DELETE, iar dacă declaşatorul este la nivel de tabelă (STATEMENT)
atunci acesta se execută o singură dată indiferent de numărul de înregistrări
afectate.
Dimensiunea unui declanşator nu poate depăşi 32 kb. Dacă totuşi se doreşte realizarea
unor prelucrări complexe în cadrul acestuia, se poate include apelul unei proceduri în corpul
declanşatorului pentru a micşora dimensiunea sa.
Sintaxa de creare a unui declanşator este următoarea:
CREATE [OR REPLACE] TRIGGER nume_trigger
[BEFORE| AFTER| INSTEAD OF]
[INSERT| [OR] | UPDATE [OF coloana,…]| [OR] | DELETE]
ON tabela
[FOR EACH ROW ]
[WHEN conditie]
CORP_TRIGGER

unde CORP_TRIGGER poate fi un bloc PL/SQL (Begin…End) sau un apel de


procedură. Procedura poate fi implementată în PL/SQL, C sau JAVA, iar apelul se realizează
utilizând instrucţiunea CALL nume_proc (fără (;) după numele său!!!)
- 106 -
Baze de date. Limbajul PL/SQL

Pentru a vedea erorile apărute la compilare se utilizează comanda:


SHOW ERRORS TRIGGER nume_trigger;

Exemplu:
Să se realizeze un trigger care se declanşează înaintea operaţiilor de adăugare în
tabela PRODUSE şi va afişa mesajul: Operaţia s-a executat cu succes!.
Declanşatorul va fi în acest caz la nivel de tabelă şi se va executa înaintea operaţiilor
de adăugare (BEFORE INSERT):
CREATE OR REPLACE TRIGGER produse_trig
BEFORE INSERT ON produse
BEGIN
dbms_output.put_line('Operatia s-a executat cu
succes!');
END;
/
show errors trigger produse_trig

Declanşatorul se va stoca în formă compilată în schema bazei de date, iar codul sursă
poate fi vizualizat şi modificat din meniul stânga - > triggers:

Figura 70 – Vizualizarea codului sursă al declanşatorilor

Clauzele INSERTING, UPDATING, DELETING se utilizeză pentru a realiza


anumite prelucrări în funcţie de evenimentul dorit (adăugare, modificare, ştergere).

Exemplu:
Să se realizeze un trigger care se declanşează la operaţiile de INSERT, DELETE sau
UPDATE pe tabela Produse. In tabela TEMP_LOG se introduce tipul operaţiei, utilizatorul
- 107 -
Baze de date. Limbajul PL/SQL

care a executat-o şi data curentă.


Pentru a prelua tipul operaţiei vom utiliza clauzele INSERTING, UPDATING,
DELETING:
-- se realizează tabela TEMP_LOG
CREATE TABLE temp_log
(tip CHAR(4),
utilizator VARCHAR2(50),
data DATE DEFAULT SYSDATE);

-- se realizează declanşatorul
CREATE OR REPLACE TRIGGER produse_trig_log
BEFORE INSERT or UPDATE or DELETE on produse
DECLARE
v_tip temp_log.tip%TYPE;
BEGIN
case
when INSERTING then v_tip :='I';
when UPDATING then v_tip:='U';
ELSE v_tip :='D';
END case;
INSERT INTO temp_log(tip, utilizator, data) VALUES
(v_tip, user, sysdate);
END;
/
show errors trigger produse_trig_log

Figura 71 – Crearea declanşatorului PRODUSE_TRIG_LOG

- 108 -
Baze de date. Limbajul PL/SQL

Verificarea declanşării trigger-ului se va realiza prin executarea unor operaţii de


manipulare a datelor:
--adaugarea unei inregistrari in tabela
insert into produse (id_produs, denumire_produs) values
(300, 'cafea');
--stergerea unei inregistrari
delete from produse where id_produs=300;
commit;
Select * from temp_log;

In tabela TEMP_LOG s-au adăugat două înregistrări aferente fiecărei operaţii de


manipulare (tipul operaţiei fiind I corespunzătoare comenzii INSERT, respectiv D
corespunzătoare comenzii DELETE):

Figura 72 – Verificarea execuţiei declanşatorului PRODUSE_TRIG_LOG

Observăm din figura de mai sus că se va afişa şi mesajul ‘Operatia s-a executat cu
succes!’, acest mesaj fiind afişat la execuţia declanşatorului PRODUSE_TRIG. În acest caz se
declanşează ambii triggeri.

Declanşatori la nivel de rând – clauza FOR EACH ROW


In declanşatorul la nivel de rând se poate accesa înregistrarea curentă procesată
folosind două pseudo-înregistrări de tip record ( :OLD, :NEW). Acestea au aceleaşi
atribute cu aceleaşi tipuri de date ca şi coloanele tabelei supusă operaţiilor de manipulare:
nume_tabela_pe_care_actioneaza_triggerul%ROWTYPE
Valorile pentru :OLD si :NEW sunt date de natura operaţiilor de manipulare:

- 109 -
Baze de date. Limbajul PL/SQL

Operaţie Valoare pentru OLD Valoare pentru NEW


INSERT NULL valoarea nouă, adăugată
UPDATE valoarea veche, anterioară actualizării valoare nouă, modificată
DELETE valoare veche, anterioară ştergerii NULL

Observaţie: Pseudo-înregistrarea (:OLD) nu este definită pentru operaţia INSERT, iar


(:NEW) nu este definită pentru operaţia DELETE.
Deşi sintactic (:OLD) şi (:NEW) sunt tratate ca tip de dată record, în realitate ele nu
sunt, iar operaţiile de atribuire directă (de exemplu var_record:=:OLD) care sunt posibile
pentru record nu sunt valide pentru (:NEW) şi (:OLD). Din acest motiv trebuie precizate exact
atributele din pseudo- înregistrare sub forma: :OLD.atribut sau :NEW.atribut.
Nu se recomandă realizarea de declanşatori la nivel de rând care utilizează valori ale
coloanelor din tabele şi care sunt supuse actualizărilor prin comenzile DML ce declanşează
triggerul. În acest caz, va apărea aceeaşi eroare ca la funcţiile utilizate în SQL şi care conţin
operaţii DML (mutating table).
Clauza WHEN se poate utiliza pentru a condiţiona execuţia unui trigger, astfel:
 clauza [when condiţie] este validă pentru triggerii la nivel de rând;
 corpul triggerului va fi executat numai pentru acele rânduri care îndeplinesc
condiţia specificată.

Exemple:
1. Să se realizeze un trigger care se execută pentru operaţiile INSERT sau UPDATE şi
care nu permite depăşirea unei limite maxime (coloana SALARIU_MAX din tabela FUNCŢII)
a salariului unui angajat pentru o anumită funcţie:
Pentru a împiedica execuţia unei operaţii de manipulare în corpul trigger-ului se va
declanşa o excepţie definită de utilizator cu ajutorul comenzilor RAISE sau
RAISE_APPLICATION_ERROR.
CREATE OR REPLACE TRIGGER restrict_salariul
BEFORE INSERT or UPDATE on angajati
FOR EACH ROW
DECLARE
v_sal_max number;
BEGIN
select salariu_max into v_sal_max from functii where
id_functie= :new.id_functie;
IF :new.salariul>v_sal_max then
RAISE_APPLICATION_ERROR (-20202, 'Nu se poate depasi
salariul maxim pentru functia data');
end if;
END;
/
show errors trigger restrict_salariul

Verificarea execuţiei se poate face printr-o operaţie de actualizare:


update angajati
set salariul =15000
where id_angajat=104;

Se observă faptul că se va declanşa excepţia ORA-20202: Nu se poate depasi


salariul maxim pentru functia data care va întrerupe execuţia operaţiilor de actualizare:

- 110 -
Baze de date. Limbajul PL/SQL

Figura 73 – Verificarea execuţiei declanşatorului RESTRICT_SALARIUL

2. Să se realizeze un declanşator care asigură unicitatea codului produsului folosind


valorile generate de o secvenţă:
--se creeaza mai intai secventa prin care se vor genera
valorile unice
CREATE SEQUENCE produse_secv
START WITH 1
INCREMENT BY 1
MAXVALUE 100
NOCYCLE;

CREATE OR REPLACE TRIGGER generare_id_produs


BEFORE INSERT ON produse
FOR EACH ROW
BEGIN
SELECT produse_secv.nextval INTO :new.id_produs FROM
dual;
END;
/
show errors trigger generare_id_produs;

- 111 -
Baze de date. Limbajul PL/SQL

Figura 74 – Crearea secvenţei şi a declanşatorului

La adăugarea unei noi înregistrări se va genera o valoare unică pentru id_produs


indiferent de valoarea precizată de utilizator:
insert into produse (id_produs, denumire_produs,
pret_lista, stoc ) values (1000, 'Monitor', 1000, 20);
select * from produse where denumire_produs like
'Monitor%';

Figura 75 – Verificarea execuţiei declanşatorului

- 112 -
Baze de date. Limbajul PL/SQL

Clauza INSTEAD OF se utilizează pentru actualizarea tabelelor din care este


construită o tabelă virtuală neactualizabilă sau parţial actualizabilă, prin care se realizează
operaţii de manipulare pe aceste tabele. La realizarea acestor operaţii, Oracle Server
declanşează triggerii pe tabelele respective.
Aceşti declanşatori sunt la nivel de rând, dar nu permit utilizarea clauzelor
BEFORE|AFTER.

Exemplu: Să se realizeze o tabelă virtuală pe tabelele CLIENTI şi COMENZI şi să se


gestioneze cu ajutorul unui declanşator operaţiile de actualizare a acesteia.
--crearea tabelei virtuale
create or replace view clienti_v as
select cl.id_client, cl.prenume_client, cl.nume_client,
cl.limita_credit,co.nr_comanda, co.data
from clienti cl, comenzi co
where cl.id_client=co.id_client;
--crearea triggerului
create or replace trigger comenzi_trigger
instead of insert or update or delete on clienti_v
for each row
begin

if inserting then
insert into clienti (id_client, prenume_client,
nume_client, limita_credit) values (:new.id_client,
:new.prenume_client, :new.nume_client,
:new.limita_credit);
insert into comenzi (nr_comanda, data, id_client)
values (:new.nr_comanda, :new.data, :new.id_client);

elsif deleting then


delete from comenzi where nr_comanda=:old.nr_comanda;

elsif updating ('nume_client') then


update clienti
set nume_client=:new.nume_client
where id_client=:old.id_client;

elsif updating ('data') then


update comenzi
set data=:new.data
where nr_comanda=:old.nr_comanda;
end if;

end;
/
show errors trigger comenzi_trigger;

Testarea execuţiei declanşatorului va arăta faptul că toate operaţiile se vor realiza


exclusiv în tabelele CLIENTI şi COMENZI:

- 113 -
Baze de date. Limbajul PL/SQL

insert into clienti_v values


(10,'Ioan','Bucur',200,100,sysdate);
insert into clienti_v values
(20,'Dana','Popa',250,110,sysdate);
select * from clienti where id_client in (10, 20);
select * from comenzi where id_client in (10, 20);

delete from clienti_v where nume_client='Bucur';


select * from clienti where id_client in (10, 20);
select * from comenzi where id_client in (10, 20);

update clienti_v
set nume_client='Popescu'
where id_client=20;
select * from clienti where id_client in (10, 20);

Figura 76 – Verificarea execuţiei declanşatorului pe tabela virtuală

Când un declanşator este creat, codul sursă este stocat în dicţionarul de date şi poate fi
vizualizat din tabela virtuală user_triggers.

Exemplu:
Să se vizulizeze toţi declanşatorii asociaţi tabelei PRODUSE:
SELECT trigger_type, trigger_name, triggering_event
FROM user_triggers
WHERE table_name='PRODUSE';

- 114 -
Baze de date. Limbajul PL/SQL

Figura 77 – Vizualizarea declanşatorilor asociaţi tabelei PRODUSE

Gestiunea declanşatorilor

Un declanşator poate fi dezactivat sau activat pentru realizarea unor operaţii mai rapid,
însă fără a încălca restricţiile la care se referă declanşatorul respectiv. Comenzile sunt:
ALTER TRIGGER nume_trigger DISABLE|ENABLE;
Sau:
ALTER TABLE nume_tabela
DISABLE|ENABLE ALL TRIGGERS;

Exemplu:
Să se dezactiveze/activeze toţi declanşatorii tabelei produse:
ALTER TABLE produse
DISABLE ALL TRIGGERS;

- 115 -
Baze de date. Limbajul PL/SQL

Figura 78 – Dezactivarea declanşatorilor asociaţi tabelei PRODUSE

Activarea acestora se face astfel:


ALTER TABLE produse
ENABLE ALL TRIGGERS;

Recompilarea unui declanşator se realizează prin:


ALTER TRIGGER nume_trigger COMPILE;

Un declanşator se şterge cu comanda:


DROP TRIGGER nume_trigger;

Observaţie: În momentul ştergerii unei tabele, se sterg automat toţi declanşatorii


asociaţi acelei tabele.

Comparaţie între declanşatori şi proceduri


Declanşatori Proceduri
Codul sursă: USER_TRIGGERS Codul sursă: USER_SOURCE
Sunt declanşaţi implicit de DML Sunt apelate în mod explicit
Nu sunt permise comenzile: COMMIT, Sunt permise comenzile: COMMIT,
ROLLBACK, SAVEPOINT, însă pot ROLLBACK, SAVEPOINT
conţine un apel de procedură în care apar
aceste comenzi, dar nu se recomandă.

Declanşatorii se stochează în formă compilată la fel ca procedurile, funcţiile şi


pachetele. Aceasta permite declanşatorilor să fie apelaţi fără recompilare.

- 116 -
Baze de date. Limbajul PL/SQL

IX.2. DECLANŞATORI PENTRU OPERAŢII DE DEFINIRE A DATELOR ŞI


PENTRU OPERAŢII LA NIVER DE SERVER

Acest tip de declanşatori este utilizat pentru controlul unor operaţii de definire a
datelor, conectare/deconectarea la nivelul schemei sau pentru oprirea/pornirea bazei de date.

Sintaxa pentru crearea unui declanşator pentru operaţiile de definire este:


CREATE [OR REPLACE] TRIGGER trigger_name
BEFORE|AFTER
CREATE [OR ALTER OR DROP]
ON DATABASE|SCHEMA
CORP_TRIGGER;

Comenzile DDL care declansează triggerul sunt cele aplicate asupra următoarelor
obiecte: CLUSTER, INDEX, PACKAGE, PROCEDURE, ROLE, SEQUENCE, SYNONYM,
TABLE, TABLESPACE, TRIGGER, TYPE, VIEW, USER.

Exemplu:
Să se realizeze un declanşator pentru monitorizarea operaţiilor de definire a datelor
realizate la nivelul schemei curente. Se va utiliza tabela TEMP_LOG în care se vor adăuga
informaţiile referitoare la operaţii:
CREATE OR REPLACE TRIGGER DDL_OP_TRIG
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
BEGIN
INSERT INTO TEMP_LOG VALUES ('DDL', USER, SYSDATE);
END;
/

Testarea declanşatorului:
create view ANG_V as select * from ANGAJATI;
select * from TEMP_LOG;

- 117 -
Baze de date. Limbajul PL/SQL

Figura 79 – Declanşarea trigger-ului DDL_OP_TRIG

Declanşatorii la nivel de server sunt declanşati la următoarele evenimente: AFTER


SERVERERROR, AFTER LOGON, BEFORE LOGOFF, AFTER STARTUP (numai pentru
baza de date), BEFORE SHUTDOWN (numai pentru baza de date).
Sintaxa este următoarea:
CREATE [OR REPLACE] TRIGGER trigger_name
BEFORE|AFTER
eveniment1 or [ev2 or...]
ON DATABASE|SCHEMA
CORP_TRIGGER;

Exemplu:
Să se creeze doi declanşatori care să monitorizeze conectarea/deconectarea
utilizatorului curent:
CREATE OR REPLACE TRIGGER CONECT_TRIG
AFTER LOGON
ON SCHEMA
BEGIN
INSERT INTO TEMP_LOG VALUES('CON' , USER, SYSDATE);
END;
/

CREATE OR REPLACE TRIGGER DECONECT_TRIG


BEFORE LOGOFF
ON SCHEMA
BEGIN
INSERT INTO TEMP_LOG VALUES('DEC' , USER, SYSDATE);
END;
/

- 118 -
Baze de date. Limbajul PL/SQL

Testarea triggerului se va face prin deconectarea şi reconectarea utilizatorului current


şi apoi prin interogarea tabelei TEMP_LOG:

select * from TEMP_LOG;

Figura 80 – Verificarea execuţiei declanşatorilor DECONECT_TRIG şi CONECT_TRIG

EXERCIŢII PROPUSE

1. Construiţi un declanşator care să nu permită adăugarea unui nou produs cu un


preţ minim mai mare de 7000.

2. Construiţi un declanşator care să se execute pentru operaţiile INSERT sau


UPDATE asupra tabelei Clienţi şi care să nu permită depăşirea unei limite de
credit date.

3. Să se monitorizeze operaţiile DDL realizate de utilizatorul curent asupra


schemei proprii. Stocaţi aceste informaţii într-o tabelă de audit.

- 119 -
Baze de date. Limbajul PL/SQL

CAPITOLUL X.
REALIZAREA DE VIDEOFORMATE ŞŞII RAPOARTE

X.1. VIDEOFORMATE

Setări la nivelul Oracle Forms:


1. se deschide instanţa Oracle Components for Java:
Start-Programs-Oracle Developer Suite – Forms Developer - Start OC4J Instance
2. se deschide componenta Forms Builder
3. se verifică următoarele setări:
Edit – Preferences – Runtime – identificaţi Web browser location (calea unde se
afla instalat browserul vostru) şi setaţi drept default Application server URL. Se
recomandă un Applicatin Server Url de forma: http://localhost:8889/forms/frmservlet.
Dacă întâmpinaţi probleme la rularea folosind Internet Explorer puteţi selecta tot aici
Default Browser alt navigator (Mozilla Firefox spre exemplu).

Proiectarea unui videoformat în Oracle se poate realiza folosind componenta Forms


Builder. Vom realiza videoformatul care prezintă departamentele şi angajaţii din cadrul
acestora, folosind un asistent de tip Wizard.
Pas 1. În cadrul asistentului Wizard pentru blocuri de date (Data Block Wizard), în
primul pas se determină tipul blocului de date, preluat dintr-o tabelă/viziune sau dintr-o
procedură stocată. Vom prelua datele dintr-o tabelă.

Figura 81 – Asistent wizard pentru crearea blocurilor de date

- 120 -
Baze de date. Limbajul PL/SQL

Pas 2. Pentru alegerea tabelei se apasă butonul Browse şi se conectează la baza de date
Oracle, specificând utilizatorul, parola şi şirul de conectare.

Figura 82 – Realizarea conectării la baza de date

Pas 3. Se vor vizualiza toate tabelele, cu posibilitatea vizualizării şi a viziunilor (prin


selectare Views). Datele vor fi preluate din tabela Departamente.

Figura 83 – Selectarea tabelei care va fi inclusă în blocul de date

- 121 -
Baze de date. Limbajul PL/SQL

Pas 4. Din lista de coloane din tabelă (Available Columns) se includ în lista de articole
din blocul de date, informaţiile cu care va lucra videoformatul (nu neapărat cele care vor fi
vizualizate).

Figura 84 – Selectarea coloanelor care vor fi incluse în blocul de date

Pas 5. Odată parcurşi toţi paşii din Data Block Wizard se poate trece automat la un alt
asistent de tip Wizard pentru vizualizarea datelor (Layout Wizard). Se determină tipul
suprafeţei pe care vor fi afişate elementele formularului (canvas): Content, Stacked, Vertical
Toolbar, Horizontal Toolbar sau Tab.

Figura 85 – Selectarea tipului de canvas


- 122 -
Baze de date. Limbajul PL/SQL

Pas 6. Din câmpurile selectate în blocul de date (Available Items) se selectează


informaţiile care vor fi vizualizate (Displayed Items), precizându-se tipul controlului (Text
Item, Pop List, Push Button, Radio Group etc.)

Figura 86 – Selectarea elementelor pentru care se vor afişa informaţii

Pas 7. Pentru fiecare câmp se specifică eticheta (Prompt), înălţimea (Width) şi


lungimea proiectată (Height).

- 123 -
Baze de date. Limbajul PL/SQL

Figura 87 – Modificarea proprietăţilor de vizualizare pentru fiecare item

Pas 8. Se alege stilul de afişare, care poate fi de tip formular (Form) sau de tip tabelar
(Tabular).

Figura 88 – Selectarea stilului de afişare

Pas 9. Se specifică titlul frame-ului în care vor fi grupate elementele, numărul de


înregistrări vizualizate la un moment dat, distanţa dintre acestea şi vizualizarea barei de
derulare.
- 124 -
Baze de date. Limbajul PL/SQL

Figura 89 – Editarea proprietăţilor de afişare

În acest moment videoformatul arată ca în figura 90:

Figura 90 – Formularul rezultat


Pentru a realiza un formular mai complex, de tipul master-detail se parcurg în
continuare şi următorii paşi.

Pas 10. Se intră din nou în asistentul Wizard pentru crearea blocurilor de date.

- 125 -
Baze de date. Limbajul PL/SQL

Figura 91 – Crearea unui nou bloc de date

Pas 11. Se reiau paşii 1-4 cu alegerea tabelei Angajati. Se creează legătura între cele
două blocuri de date (Create Relationship), bazată pe o condiţie de tip join.

Figura 92 – Crearea legăturii între blocurile de date Departamente şi Angajati

Pas 12. Se refac paşii 5- 7 şi se alege stilul de afişare tabelar.

Pas 13. Se reia pasul 9, cu specificarea numărului de înregistrări vizualizate.

- 126 -
Baze de date. Limbajul PL/SQL

Figura 93 – Editarea proprietăţilor de afişare

În acest moment videoformatul generat arată ca în figura 94:

Figura 94 – Formularul rezultat

În continuare, pentru a adăuga facilităţi sporite formularului creat, includem un buton


la apăsarea căruia să se realizeze adăugarea unui nou angajat în baza de date. În acest scop,
selectăm un buton din bara de instrumente, iar apoi îl desenăm pe ecran:

- 127 -
Baze de date. Limbajul PL/SQL

Figura 95 – Adăugarea unui item de tip buton

Pentru a schimba eticheta, se efectuează click dreapta pe buton şi se alege Property


Palette. În această fereastră apar specificate toate proprietăţile butonului, cu posibilitatea de a
fi modificate. Pentru îndeplinirea obiectivului modest pe care ni l-am propus, se schimbă
proprietatea Label:

Figura 96 – Modificarea propretăţilor în Property Palette

- 128 -
Baze de date. Limbajul PL/SQL

Pentru a adăuga funcţionalitate butonului creat, vom crea un trigger care să se


declanşeze la apăsarea acestuia (When-Button-Pressed):

Figura 97 – Selectarea trigger-ului When-Button-Pressed

Codul pentru adăugarea unei noi înregistrări în tabela Angajati se va specifica în


fereastra PL/SQL Editor:

Figura 98 – Adăugarea codului pentru trigger

Pentru ca înregistrările existente în tabele să fie accesate direct prin videoformat la


momentul rulării, la nivelul acestuia se creează un nou trigger: When-New-Form-Instance:

- 129 -
Baze de date. Limbajul PL/SQL

Figura 99 – Selectarea trigger-ului When-New-Form-Instance

Codul PL/SQL aferent declanşatorlului de tipul When-New-Form-Instance:

Figura 100 – Adăugarea codului pentru trigger

Pentru introducerea unei imagini pe fundalul videoformatului se va selecta opţiunea


Import din meniul Edit:

- 130 -
Baze de date. Limbajul PL/SQL

Figura 101 – Selectarea opţiunii de adăugare a unei imagini de fundal

În urma rulării, se va obţine videoformatul din figura 102:

Figura 102 – Videoformatul final

- 131 -
Baze de date. Limbajul PL/SQL

X.2. RAPOARTE

Setări la nivelul Oracle Reports:


1. se deschide instanţa Oracle Components for Java:
Start-Programs-Oracle Developer Suite – Reports Developer - Start OC4J
Instance
2. se deschide componenta Reports Builder

Drept exemplu, vom proiecta raportul Angajati în Oracle Reports Builder, utilizând
asistentul de tip Report Wizard.

Pas 1. Se specifică titlul şi tipul raportului. Raportul pe care îl realizăm va afişa


angajaţii pentru fiecare departament în parte, calculând şi numărul acestora, cu încadrarea
elementului de grupare la stanga:

Figura 103 – Selectarea tipului de raport

- 132 -
Baze de date. Limbajul PL/SQL

Pas 2. Preluarea datelor din tabele se va realiza printr-o interogare SQL:

Figura 104 – Preluarea datelor din tabele

Pas 3. Pentru selectarea tabelei este necesară conectarea la baza de date. Se scrie fraza
SELECT, se preia dintr-o cerere salvată sau se construieşte cu ajutorul componentei Query
Builder. În cadrul Query Builder se includ tabelele din care se preiau datele, în cazul nostru:
Angajati, Departamente.

Figura 105 – Utilizarea Query Builder

- 133 -
Baze de date. Limbajul PL/SQL

Pas 5. Cererea creată poate fi salvată pentru o utilizare ulterioară. Se revine în Report
Wizard şi se vizualizează interogarea creată.

Figura 106 – Cererea SQL creată cu Query Builder

Pas 6. Se specifică după care dintre câmpurile selectate se va face gruparea


înregistrărilor:

Figura 107 – Selectarea câmpului de grupare

- 134 -
Baze de date. Limbajul PL/SQL

Pas 7. Se specifică acele câmpuri care vor fi vizualizate în raport:

Figura 108 – Selectarea câmpurilor afişate în raport

Pas 8. Se pot calcula totaluri de tipul: sumă, medie aritmetică, număr de înregistrări,
valoarea minimă, valoarea maximă, procente din total. În cazul raportului exemplificat ne
interesează numarul de angajaţi din fiecare departament:

Figura 109 – Includerea de funcţii de grup

- 135 -
Baze de date. Limbajul PL/SQL

Pas 9. Pentru fiecare câmp introdus în raport se defineşte eticheta şi numărul de


caractere de afişat:

Figura 110 – Editarea proprietăţilor de afişare

Pas 10. Pentru stabilirea culorilor folosite, a caracterelor, a tipului liniilor sau a
structurii de aranjare a informaţiei se poate prelua un şablon (template) predefinit în Oracle
sau unul creat de utilizator. Se recomandă crearea unui şablon pe baza căruia să se genereze
toate rapoartele:

Figura 111 – Selectarea unui şablon pentru raport

- 136 -
Baze de date. Limbajul PL/SQL

Raportul Total angajati pe departamente generat este cel din figura 112. Ulterior
realizării raportului cu ajutorul Report Wizard câmpurile pot fi rearanjate şi reformatate.

Figura 112 – Raportul Total angajati pe departamente

X.3. INTEGRARE FORMS-REPORTS

În vederea realizării de aplicaţii complexe, se pot elabora videoformate în cadrul cărora să


poată fi apelate rapoarte, simple sau parametrizate. În acest scop, se parcurg următorii paşi.

Pas 1. Se adaugă un server de Reports:


Start->Run->rwserver server=rwmaster
Va apărea o fereastră ca şi cea din figura 113:

Figura 113 – Adăugarea unui server de Reports

- 137 -
Baze de date. Limbajul PL/SQL

Pas 2. In videoformat se adaugă un obiect de tip Reports. În cadrul videoformatului, pe Item-


ul Reports apăsăm add (butonul +), selectăm Use Existing Reports File->Browse-
>C:\CulegerePL\raport.jsp

Figura 114 – Adăugarea unui obiect de tip Reports

Pas 3. Adăugăm un buton nou în videoformat: Lansare Raport. Pe declanşatorul When-


Button-Pressed introducem următorul cod sursă:

declare
report_id Report_Object;
ReportServerJob VARCHAR2(100);
vc_rep_status VARCHAR2(100);
vjob_id VARCHAR2(100);
repsvr varchar2(21) := 'rwmaster';

BEGIN

report_id:= find_report_object('REPORT19');
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_COMM_MODE,S
YNCHRONOUS);
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_DESTYPE,fil
e);
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_DESNAME,'rp
out1.html');
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_DESFORMAT,'
HTMLCSS');

- 138 -
Baze de date. Limbajul PL/SQL

SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_SERVER,reps
vr);
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_OTHER,
'paramform=no');

ReportServerJob:=run_report_object(report_id);
vjob_id := substr(reportserverjob,length(repsvr)+2);
vc_rep_status := REPORT_OBJECT_STATUS(ReportServerJob);

WHILE vc_rep_status in
('RUNNING','OPENING_REPORT','ENQUEUED', null)
LOOP
vc_rep_status:=REPORT_OBJECT_STATUS(ReportServerJob);
END LOOP;

IF vc_rep_status='FINISHED' THEN
WEB.SHOW_DOCUMENT('/reports/rwservlet/getjobid'||
vjob_id ||'?server='||repsvr,'_blank');
ELSE
message ('Report failed with error message '||
vc_rep_status);
END IF;
END;

Putem încerca să rulăm videoformatul şi să apăsăm pe butonul Lansare Raport.


Observaţie: Dacă primim un mesaj de eroare cum că nu se poate realiza conexiunea la server,
trebuie să realizăm şi următorii paşi:

Pas 4. Dacă nu avem un IP fix se recomandă adăugarea unei interfeţe de tip Microsoft
Loopback cu un astfel de IP:
Control Panel->Add Hardware->Add a new Hardware device-> Install the hardware
manually->Network Adapter->Microsoft->Microsoft Loopback Adapter.
Asignăm acestei interfeţe adresa 192.168.1.1

Pas 5. Dacă Oracle Developer este instalat în D:\OracleDeveloper, modificăm fişierul


D:\OracleDeveloper\reports\conf\rwnetwork.conf astfel:

<?xml version="1.0" encoding="ISO-8859-1"?>


<!DOCTYPE discoveryService SYSTEM
"file:D:\OracleDeveloper/reports/dtd/rwnetworkconf.dtd"
>
<discoveryService>
<!--multicast channel="228.5.6.7" port="14021"
timeout="1000" retry="3"/-->
<namingService name="Cos" host="192.168.1.1"
port="14021"/>
</discoveryService>

Asiguraţi-vă că portul 14021 nu este blocat. Restartăm calculatorul. Repornim serverul de


reports.

Pas 6. Din Command Window, D:\OracleDeveloper\BIN, lansăm comanda:


namingservice.bat 14021
- 139 -
Baze de date. Limbajul PL/SQL

Ar trebui să apară mesajul care să specifice faptul că pornirea s-a realizat cu succes. Nu
închideţi fereastra respectivă.
Putem verifica dacă serverul funcţioneză rulând următoarea comandă:
rwdiag -findall

Figura 115 – Verificarea funcţionării serverului

Pas 7. Pornim OC4J şi lansăm din nou videoformatul:

Figura 116 – Videoformatul din cadrul căruia se apelează raportul

- 140 -
Baze de date. Limbajul PL/SQL

Apăsăm Lansare Raport şi dacă nu exista vreun Pop-Up Blocker activ ar trebui să se deschidă
un nou tab cu raportul apelat:

Figura 117 – Raportul apelat prin formular

Puteţi verifica job-urile procesate de serverul de Reports accesând următorul link:


http://localhost:8889/reports/rwservlet/showjobs?server=rwmaster

- 141 -
Baze de date. Limbajul PL/SQL

Figura 118 – Fereastră de administrare a serverului

Observaţie: Dacă dorim transmiterea unui parametru între videoformat şi raport urmăm şi
următorii doi paşi:

Pas 8. Modificăm interogarea din Reports:

SELECT ALL ANGAJATI.ID_ANGAJAT, ANGAJATI.PRENUME,


ANGAJATI.NUME, ANGAJATI.EMAIL,
DEPARTAMENTE.DENUMIRE_DEPARTAMENT
FROM ANGAJATI, DEPARTAMENTE
WHERE (ANGAJATI.ID_DEPARTAMENT =
DEPARTAMENTE.ID_DEPARTAMENT)
AND ANGAJATI.ID_DEPARTAMENT=:PID_DEP

Figura 119 – Parametrizarea cererii SQL

- 142 -
Baze de date. Limbajul PL/SQL

Pas 9. In videoformat modificăm codul de pe butonul Lansare Raport astfel:

declare
report_id Report_Object;
ReportServerJob VARCHAR2(100);
vc_rep_status VARCHAR2(100);
vjob_id VARCHAR2(100);
pl_id ParamList;
repsvr varchar2(21) := 'rwmaster';

BEGIN

if :departamente.id_departament is null then


message('Nu ati selectat niciun angajat!');
return;
end if;

report_id:= find_report_object('REPORT19');
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_COMM_MODE,S
YNCHRONOUS);
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_DESTYPE,fil
e);
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_DESNAME,'rp
out1.html');
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_DESFORMAT,'
HTMLCSS');
SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_SERVER,reps
vr);
--SE COMENTEAZĂ SAU SE ŞTERGE LINIA URMĂTOARE
--SET_REPORT_OBJECT_PROPERTY(report_id,REPORT_OTHER,
'paramform=no');

pl_id := get_parameter_list('TEMP');
if id_null(pl_id) then pl_id :=
create_parameter_list('TEMP');
else destroy_parameter_list('TEMP');
pl_id := create_parameter_list('TEMP');
end if;
Add_Parameter('TEMP','PID_DEP',TEXT_PARAMETER,:departam
ente.id_departament);

ReportServerJob:=run_report_object(report_id,'TEMP');
vjob_id := substr(reportserverjob,length(repsvr)+2);
vc_rep_status := REPORT_OBJECT_STATUS(ReportServerJob);

WHILE vc_rep_status in
('RUNNING','OPENING_REPORT','ENQUEUED', null)
LOOP
vc_rep_status:=REPORT_OBJECT_STATUS(ReportServerJob);
END LOOP;

- 143 -
Baze de date. Limbajul PL/SQL

IF vc_rep_status='FINISHED' THEN
WEB.SHOW_DOCUMENT('/reports/rwservlet/getjobid'||
vjob_id ||'?server='||repsvr,'_blank');
ELSE
message ('Report failed with error message '||
vc_rep_status);
END IF;
END;

Când apăsăm pe butonul Lansare Raport se va rula raportul doar pentru departamentul
angajatului curent din videoformat:

Figura 120 – Raport parametrizat pentru departamentul cu denumirea Administration

EXERCIŢII PROPUSE

1. Creaţi un formular prin care să se evidenţieze pentru fiecare comandă încheiată,


produsele din cadrul acesteia.

2. Realizaţi un raport care să afişeze produsele incluse în fiecare comandă,


precum şi numărul acestora, pentru fiecare grup şi per total.

3. Adăugaţi pe formularul creat la exerciţiul 1 un buton, la a cărui apăsare să se


apeleze raportul realizat la exerciţiul 2.

- 144 -
Baze de date. Limbajul PL/SQL

CAPITOLUL XI.
EXERCIŢII RECAPITULATIVE

CERINŢE PROPUSE

1. Creaţi un bloc PL/SQL care să afişeze codul şi denumirea pentru departamentul din
localitatea al cărei cod (id_produs) este precizat (fie citit de la tastatură, fie dat drept
valoare iniţială unei variabile locale). Trataţi excepţiile predefinite care pot apărea.

2. Creaţi un bloc PL/SQL care să afişeze informaţii despre angajaţi şi comenzile


intermediate de către aceştia (atributele id_angajat, nume, nr_comanda, data). Sortaţi
descendent în funcţie de numele angajatului înregistrările obţinute.

3. Creaţi un bloc PL/SQL care să afişeze informaţii despre angajaţii care nu au încheiat
comenzi. Sortaţi descendent în funcţie de numele angajaţilor.

4. Creaţi un bloc PL/SQL prin care se dublează preţul produsului (pret_lista) al cărui cod
este 3169. În cazul în care acesta nu există (comanda UPDATE nu realizează nicio
modificare) se invocă o excepţie (pe care o definiţi în mod explicit).

5. Creaţi un bloc PL/SQL prin care să se dubleze preţul (pret_lista) pentru produsele din
categoriile hardware2 şi hardware3. În cazul în care actualizarea se realizează cu succes,
afişaţi numărul de înregistrări modificate, iar în caz contrar invocaţi o excepţie!

6. Creaţi un bloc PL/SQL care să afişeze informaţii despre clienţii care au încheiat comenzi
în anul 1999.

7. Să se creeze un pachet care să conţină o funcţie şi o procedură publice, care calculează


valoarea totală a unei comenzi, respectiv numărul de zile de când a fost înregistrată o
comandă. Trataţi şi excepţia NO_DATA_FOUND. Să se apeleze subprogramele într-un
bloc anonim.

8. Într-un bloc anonim, citiţi de la tastatură id-ul unui produs şi pentru acesta afişaţi toate
comenzile corespunzătoare.

9. Utilizând un cursor explicit, afişaţi toţi salariaţii din tabela ANGAJAŢI care lucrează într-
un anumit departament (id_departament) primit ca parametru.

10. Să se afişeze printr-o funcţie informaţii despre clienţii de sex masculin. Funcţia să
returneze numărul acestora. Să se trateze şi o excepţie definită de utilizator. Să se apeleze
funcţia.

11. Afişati folosind un cursor toate comenzile care au fost date în ultimele 3 luni.

12. Construiţi un trigger care să nu permită existenţa unei comenzi cu o cantitate comandată
mai mare de 1000.

- 145 -
Baze de date. Limbajul PL/SQL

13. Într-un bloc PL/SQL ştergeţi toate produsele care au pret_lista mai mare de 1500.
Folosind o excepţie trataţi cazul în care nu exista nici un produs care îndeplineşte aceasta
condiţie, afişând un mesaj.

14. Să se afişeze, folosind un cursor, de câte ori a fost comandat fiecare produs care face parte
din categoria hardware2.

15. Construiţi un trigger care să nu permită adăugarea unui nou client cu o limită de credit mai
mică de 50.

16. Să se afişeze cele mai mari trei comenzi, ca valoare, date de clienţii de sex M, utilizând un
cursor.

17. Să se construiască o funcţie care primeşte ca parametru un an calendaristic (spre exemplu


2000) şi returnează numărul de clienţi născuţi în respectivul an.

18. Creaţi un bloc PL/SQL în care să utilizaţi un cursor explicit pentru a afişa valoarea
fiecărei comenzi şi data la care aceasta a fost încheiată.

19. Creaţi o funcţie care primeşte ca parametru nr_comanda şi returnează numărul de


produse din cadrul acesteia.

20. Realizaţi o procedură care primeşte ca parametru id_functie şi, pentru fiecare angajat cu
această funcţie, realizează o mărire de salariu cu 10%. Procedura va afişa numărul de
angajaţi pentru care s-a actualizat salariul.

21. Realizaţi un declanşator pe tabela angajaţi care să nu permită finalizarea operaţiilor de


INSERT sau UPDATE în cazul în care salariul unui angajat este mai mare de 30000.

- 146 -
Baze de date. Limbajul PL/SQL

TESTE GRILĂ PROPUSE*

1. Limbajul PL/SQL:
a) este un limbaj de programare procedural
b) funcţionează stil compilator
c) funcţionează stil interpretor
d) este un limbaj de programare universal
e) este un limbaj de descriere a datelor.

2. Din următoarea structură de bloc PL/SQL:


DECLARE
instrucţiuni;
BEGIN
instrucţiuni;
EXCEPTION
instrucţiuni;
END;
a) sunt obligatorii toate părţile
b) sunt opţionale toate părţile
c) lipseşte partea pentru tratarea erorilor
d) este obligatorie doar partea executabilă
e) este obligatorie doar partea declarativă.

3. Atributele unui cursor explicit sunt:


a) ROWCOUNT, FOUND, ISOPEN
b) ROWNUMBER, NOTFOUND, FOUND
c) ISCLOSE, FOUND, ROWCOUNT
d) ISOPEN, NOTFOUND, ROWCOUNT
e) FETCH, CLOSE, OPEN.

4. Precizaţi care dintre următoarele afirmaţii sunt adevărate:


a) SGBD este un ansamblu complex de programe care asigură interfaţa între o
bază de date şi utilizatorii acesteia
b) arhitectura pe componente a unui SGBD este compusă din: nucleu, interfeţe
şi instrumente
c) SGBD este componenta software a unui sistem de bază de date care
interacţionează cu toate celelalte componente ale acestuia, asigurând
legătura şi interdependenţa între elementele sistemului
d) funcţiile de bază ale unui SGBD sunt: descriere, manipulare,
utilizare, administrare
e) obiectivul general al unui SGBD este de a furniza suportul software
complet pentru dezvoltarea de aplicaţii informatice cu baze de date.

5. SGBD are ca obiective:

*
Notă: Întrebările pot avea oricâte răspunsuri corecte!

- 147 -
Baze de date. Limbajul PL/SQL

a) asigurarea independenţei datelor; asigurarea partajabilităţii datelor; asigurarea


unor facilităţi sporite de utilizare a datelor;
b) asigurarea partajabilităţii datelor; asigurarea unor facilităţi de utilizare a
datelor; asigurarea independenţei logice şi fizice a utilizatorilor;
c) asigurarea unei redundanţe medii; asigurarea unor facilităţi de utilizare a
datelor; asigurarea protecţiei datelor;
d) asigurarea sporirii gradului de securitate a datelor; asigurarea integrităţii
datelor; asigurarea unei redundanţe minime şi controlate;
e) asigurarea securităţii datelor; asigurarea partajabilităţii datelor; asigurarea
unor legături între schemele utilizatorilor.

6. Se consideră următoarea secvenţă de program:


DECLARE
CURSOR cursor1 IS SELECT id_angajat, nume FROM Angajati;
vid angajati.id_angajat%TYPE;
vnume CHAR (20);
BEGIN
OPEN cursor1;
LOOP
FETCH cursor1 INTO vid,vnume;
EXIT WHEN cursor1% NOTFOUND;
END LOOP;
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) lucrează cu un cursor explicit
b) lucrează cu un cursor implicit
c) lucrează cu un cursor şi o tabelă virtuală
d) parcurge secvenţial o zonă de memorie internă
e) execută o cerere de regăsire.

7. Se consideră următorul bloc PL/SQL:


DECLARE
v NUMBER(20):=100;
BEGIN
NULL;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(v);
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) blocul declară şi iniţializează o variabilă numerică
b) blocul se execută, dar nu afişează nimic
c) blocul nu se execută deoarece nu sunt instrucţiuni executabile
d) execuţia blocului afişează întotdeauna variabila v cu valoarea 100
e) blocul este eronat.

8. Se consideră tabela Angajaţi şi următorul bloc PL/SQL:


CREATE OR REPLACE TRIGGER t_angajati
BEFORE DELETE ON Angajati

- 148 -
Baze de date. Limbajul PL/SQL

FOR EACH ROW


BEGIN
RAISE_APPLICATION_ERROR (-20100,'Operatie interzisa!');
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) blocul realizează un declanşator
b) blocul realizează o procedură
c) blocul şterge înregistrări din tabela Angajati
d) declanşatorul se execută pentru fiecare înregistrare din tabela Angajati
e) blocul declanşează o excepţie utilizator pentru a împiedica ştergerea înregistrărilor
din tabela Angajaţi.

9. Un SGBD relaţional asigură securitatea datelor prin:


a) proceduri de criptare
b) acces concurent la date
c) utilizarea viziunilor
d) proceduri automate de restaurare a datelor în caz de incidente
e) proceduri speciale de salvare a datelor.

10. Un SGBD este considerat minimal relaţional dacă îndeplineşte criteriile:


a) toate datele din BD sunt reprezentate ca tabele
b) sunt implementate cel puţin restricţiile de integritate: cheie unică şi cheie externă
c) sunt implementaţi operatorii relaţionali pe mulţimi: reuniune, intersecţie, diferenţă
d) nu există pointeri fizici între tabele
e) sunt implementaţi operatorii relaţionali: proiecţie, selecţie, joncţiune.

11. Printre arhitecturile de referinţă ale SGBD se numără:


a) arhitectura SGBD ORACLE
b) arhitectura pe etaje
c) ANSI/SPARC
d) CODASYL
e) arhitectura gaudiană grid-computing.

12. SGBD permite realizarea următoarelor activităţi:


a) definirea structurii bazei de date
b) construirea interfeţelor grafice
c) accesul la date
d) protecţia datelor
e) întreţinerea bazei de date.

13. Atributele unui cursor explicit sunt:


a) ROWCOUNT, FOUND, ISOPEN
b) ROWNUMBER, NOTFOUND, FOUND
c) ISCLOSE, FOUND, ROWCOUNT
d) ISOPEN, NOTFOUND, ROWCOUNT
e) FETCH, CLOSE, OPEN.

14. Se dă următoarea secvenţă de comenzi:


CREATE OR REPLACE PROCEDURE modifica (p_id IN clienti.id_client%TYPE)
IS

- 149 -
Baze de date. Limbajul PL/SQL

BEGIN
UPDATE clienti SET limita_credit=2500 WHERE id_client=p_id;
END;
/

CREATE OR REPLACE PROCEDURE exemplu


IS
v_id clienti.id_client%TYPE;
CURSOR c1 IS SELECT id_client FROM clienti;
BEGIN
OPEN c1;
FOR i IN 1..4 LOOP
FETCH c1 INTO v_id;
modifica(v_id);
END LOOP;
END;
/
EXECUTE exemplu;

Care dintre următoarele afirmaţii sunt corecte:


a) se creează două proceduri stocate
b) se va genera eroare deoarece variabila i folosită în structura repetitivă nu a
fost definită
c) se utilizează apelul unei proceduri din altă procedură
d) se modifică limita de credit a primilor 4 angajaţi
e) se va genera eroare deoarece procedura exemplu nu are parametri.

15. Se dă următoarea secvenţă de comenzi:


CREATE TABLE mesaje (cod NUMBER(6), nume VARCHAR2(20));
DECLARE
v_id clienti.id_client%TYPE;
v_nume clienti.nume_client%TYPE;
CURSOR c1 IS SELECT id_client, nume_client FROM clienti;

BEGIN
OPEN c1;
FOR i IN 1 .. 4 LOOP
FETCH c1 INTO v_id, v_nume;
INSERT INTO mesaje VALUES(v_id, v_nume);
END LOOP;
CLOSE c1;
END;
/

Care afirmaţii sunt eronate:


a) defineşte un cursor explicit
b) utilizează o structură de control repetitivă
c) creează o tabelă
d) testează un cursor implicit
e) defineşte două variabile.

- 150 -
Baze de date. Limbajul PL/SQL

16. În PL/SQL, un declanşator este executat:


a) prin apel cu o instrucţiune CALL
b) prin denumirea sa
c) la apariţia unei declaraţii
d) la apariţia unui eveniment
e) declanşatorul se foloseşte şi în SQL.

17. Un SGBD:
a) este un limbaj de regăsire
b) deţine întotdeauna SQL
c) este componenta software a unui sistem de baze de date
d) este o bază de date
e) asigură gestiunea tuturor fişierelor de pe un sistem de calcul.

18. Ce concepte sunt specifice implementării modelului de date într-un SGBDR:


a) relaţie
b) tabelă
c) tuplu
d) coloană
e) atribut.

19. Funcţiile unui SGBD sunt:


a) definirea programelor
b) descrierea datelor
c) manipularea programelor
d) administrarea datelor
e) utilizarea sistemului informaţional.

20. Obiectivele unui SGBD sunt:


a) asigurarea unor facilităţi sporite de utilizare a datelor
b) asigurarea independenţei datelor
c) regăsirea rapidă a datelor
d) prelucrarea datelor prin operaţii de selecţie, ordonare, interclasare
e) asigurarea unei redundanţe sporite.

21. O excepţie PL/SQL este:


a) un tip de dată
b) un identificator asociat unei condiţii anormale apărute în timpul execuţiei
unui bloc PL/SQL
c) o eroare în general
d) un bloc de tratare a unei erori în execuţie
e) declarată prin cuvântul cheie EXCEPTION.

22. Structura unui bloc PL/SQL poate conţine:


a) BEGIN
b) DECLARE
c) LOOP
d) VARIABLE
e) EXCEPTION.

- 151 -
Baze de date. Limbajul PL/SQL

23. Se dă următoarea declaraţie:


DECLARE
v_nr NUMBER :=10;
v_rez NUMBER;

Care dintre următoarele variante atribuie valoarea 50 variabilei v_rez?


a) v_rez:=v_nr*5
b) v_rez:=100/2
c) v_rez:=ROUND(49.78)
d) v_rez:=TRUNC(49.78)
e) nicio varianta.

24. Atributul %FOUND caracterizează:


a) o înregistrare
b) un cursor implicit
c) o tabelă virtuală
d) un cursor explicit
e) o excepţie predefinită.

25. Care din următoarele activităţi sunt specifice SGBD:


a) definirea structurii bazei de date
b) construirea interfeţelor grafice
c) accesul la date
d) protecţia datelor
e) întreţinerea bazei de date.

26. Care din următoarele aspecte ţin de asigurarea securităţii datelor:


a) autorizarea şi controlul accesului
b) utilizarea tabelelor virtuale
c) accesul concurent
d) salvarea / restaurarea
e) partajabilitatea datelor.

27. Ce variante nu constituie o funcţie a SGBD:


a) descrierea datelor
b) manipularea datelor
c) utilizarea datelor
d) administrarea datelor
e) administrarea aplicaţiilor

28. Care din următoarele noţiuni nu sunt folosite în teoria relaţională, fiind specifice
SGBDR:
a) relaţie
b) tabelă
c) tuplu
d) coloană
e) atribut.

29. Instrucţiunile şi mecanismele utilizate în cadrul SGBDR sunt:


a) limbajele relaţionale
b) optimizarea cererilor la distanţă

- 152 -
Baze de date. Limbajul PL/SQL

c) mecanisme de descriere a datelor


d) mecanisme de manipulare a datelor
e) protecţia datelor.

30. Se dă următoarea secvenţă de comenzi:


i := 2;
WHILE i < 3 LOOP
i := 4;
DBMS_OUTPUT.PUT_LINE('Valoarea contorului i este: ' || i);
END LOOP;
Câte linii rezultat vor fi afişate:
a) nici una
b) una
c) două
d) blocul nu se va executa deoarece nu putem folosi dbms_output.put_line în
cadrul unei bucle
e) blocul se va executa la infinit.

31. Care dintre următoarele construcţii sunt corecte?


a) EXCEPTION
WHEN NO_DATA_FOUND THEN secv_comenzi1;
WHEN OTHERS THEN statement_2;
END.
b) EXCEPTION
WHEN TOO_MANY_ROWS THEN secv_comenzi1;
END.
c) EXCEPTION
WHEN NO_DATA_FOUND THEN secv_comenzi1;
WHEN NO_DATA_FOUND THEN secv_comenzi2;
WHEN OTHERS THEN secv_comenzi3;
END.
d) EXCEPTION
WHEN OTHERS THEN secv_comenzi1;
END.
e) EXCEPTION
WHEN OTHERS THEN secv_comenzi1;
WHEN NO_DATA_FOUND THEN secv_comenzi2;
END.

32. Ce tipuri de excepţii trebuie invocate în mod explicit:


a) excepţii definite de utilizator
b) excepţii predefinite ale serverului Oracle
c) excepţii non-predefinite ale serverului Oracle
d) toate cele de mai sus
e) nici una din cele de mai sus.

33. Funcţiile unui SGBD sunt:


a) descrierea datelor
b) manipularea datelor
c) protecţia datelor
d) protecţia utilizatorilor
- 153 -
Baze de date. Limbajul PL/SQL

e) administrarea datelor.

34. Obiectivele unui SGBD sunt:


a) asigurarea unei redundanţe minime şi controlate a datelor
b) asigurarea dependenţei datelor faţă de aplicaţii
c) asigurarea protecţiei datelor
d) asigurarea resurselor materiale
e) asigurarea partajabilităţii datelor.

35. Dintre instrumentele şi mecanismele de lucru de care dispune un SGBDR sunt:


a) limbajele relaţionale
b) mecanisme pentru optimizarea cererilor de date
c) mecanisme pentru gestiunea resurselor materiale
d) mecanisme pentru asigurarea coerenţei datelor
e) mecanisme pentru controlul integrităţii datelor.

36. Limbajul PL/SQL nu suportă comenzile:


a) ALTER
b) CREATE, DROP
c) GRANT
d) DELETE, UPDATE
e) COMMIT, SAVEPOINT

37. Funcţia de manipulare a datelor presupune:


a) actualizarea bazei de date
b) prelucrarea datelor
c) asigurarea integrităţii logice a datelor
d) regăsirea datelor
e) salvarea/restaurarea datelor.

38. Se dă următoarea secvenţă de comenzi:


SET SERVEROUTPUT ON
DECLARE
nota_examen NUMBER;
BEGIN
IF nota_examen >= 5 THEN
DBMS_OUTPUT.PUT_LINE('promovat!');
ELSE
DBMS_OUTPUT.PUT_LINE('nepromovat!');
END IF;
END;
/
Care afirmaţii sunt eronate:
a) se afişează promovat!
b) se afişează nepromovat! dacă nota obţinută este mai mică decât 5 şi promovat!
în caz contrar
c) nu se va afişa nimic
d) se va afişa NULL
e) se afişează nepromovat!.

39. Se dă următoarea secvenţă de comenzi:

- 154 -
Baze de date. Limbajul PL/SQL

SET SERVEROUTPUT ON
BEGIN
UPDATE comenzi SET data=TRUNC(SYSDATE);
DBMS_OUTPUT.PUT_LINE (SQL%ROWCOUNT);
ROLLBACK;
EXCEPTION
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Eroare');
END;
/
Presupunem că tabela Comenzi are 5 înregistrări. În aceste condiţii, blocul PL/SQL
afişează:
a) 5
b) 'Eroare'
c) nu afişează nimic
d) dă eroare la compilare
e) 0.

40. Pentru un cursor folosit într-un ciclu FOR se vor elimina operaţiile explicite de:
a) declarare a cursorului
b) deschidere a cursorului
c) încărcare de date din linia curentă din cursor în variabile
d) închidere a cursorului
e) toate operaţiile de mai sus.

41. Sistemul de gestiune a bazelor de date:


a) este un ansamblu complex de programe care asigură interfaţa între o bază de
date şi utilizatorii acesteia
b) este componenta software a unui sistem de baze de date
c) interacţionează cu toate componentele unui SBD
d) asigură interdependenţa elementelor componente ale unui SBD
e) este o bază de date cu facilităţi extinse de programare.

42. Se dă următoarea secvenţă PL/SQL :


DECLARE
p clienti%ROWTYPE;
CURSOR c2 IS SELECT * FROM clienti WHERE limita_credit > 1000
ORDER BY limita_credit DESC;
BEGIN
FOR p IN c2 LOOP
DBMS_OUTPUT.PUT_LINE('clientul '||p. nume_client ||' are limita de credit: '||p.
limita_credit);
EXIT WHEN c2%ROWCOUNT>=5;
END LOOP;
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) secvenţa utilizează un cursor explicit prin care se parcurg clienţii cu limita
de credit mai mare de 1000
b) secvenţa va afişa o eroare deoarece nu se poate ieşi cu exit dintr-un ciclu FOR
c) secvenţa afişează un top al primilor 5 clienţi cu limita de credit cea mai mare
d) secvenţa utilizează incorect instrucţiunea c2%ROWCOUNT

- 155 -
Baze de date. Limbajul PL/SQL

e) variabila p din secvenţă este de tip record.

43. Implementarea tabelelor virtuale (views) în sistemul Oracle asigură:


a) reprezentarea schemelor externe ale bazei de date
b) partiţionarea fizică a bazei de date
c) viziunea utilizatorilor asupra bazei de date
d) securitatea datelor
e) integritatatea datelor.

44. După modelul logic de date implementat, SGBD-urile sunt:


a) distribuite
b) relaţionale
c) reţea
d) cu limbaj propriu de programare
e) pentru microcalculatoare.

45. Regulile lui Codd:


a) stabilesc în ce măsură un SGBD este complet relaţional
b) stabilesc dacă un SGBD este pseudo-relaţional
c) exprimă cerinţele pentru ca un SGBD să fie minimal relaţional
d) stabilesc dacă un SGBD este relaţional-obiectual
e) evaluează performanţele unui SGBD.

46. Se dă următoarea secvenţă de comenzi:


SET SERVEROUTPUT ON
DECLARE
n CONSTANT VARCHAR2 (5):=2;
a BOOLEAN DEFAULT FALSE;
BEGIN
IF n BETWEEN -1 AND 1 OR a THEN DBMS_OUTPUT.PUT_LINE('A');
ELSIF n IS NOT NULL AND NOT a THEN DBMS_OUTPUT.PUT_LINE('B');
ELSE
DBMS_OUTPUT.PUT_LINE('C');
END IF;
END;
/
Blocul PL/SQL afişează:
a) B
b) C
c) A
d) un mesaj de eroare deoarece nu se foloseşte corect constanta
e) un mesaj de eroare deoarece nu se defineste corect variabila booleană.

47. Următoarele afirmaţii sunt adevărate:


a) pseudo înregistrarea :new are valoarea NULL pentru operaţii de INSERT
b) pseudo înregistrarea :new are valoarea NULL pentru operaţii de UPDATE
c) pseudo înregistrarea :old are valoarea NULL pentru operaţii de INSERT
d) pseudo înregistrarea :old are valoarea NULL pentru operaţii de UPDATE
e) pseudo înregistrarea :old are valoarea NULL pentru operaţii de DELETE.

48. PL/SQL Oracle acceptă următoarele tipuri de subprograme:

- 156 -
Baze de date. Limbajul PL/SQL

a) funcţii
b) proceduri
c) tabele virtuale
d) pachete
e) declanşatori.

49. SGBD asigură realizarea următoarelor activităţi:


a) definirea structurii bazei de date
b) gestiunea resurselor sistemului de calcul
c) încărcarea bazei de date
d) întreţinerea bazei de date
e) accesul la date.

50. SGBD Oracle 10g implementează modelul de date:


a) relaţional-reţea
b) ierarhic
c) reţea
d) relaţional-obiectual
e) orientat-obiect.

51. Se consideră următorul bloc PL/SQL:


DECLARE
procent number(2):=15;
BEGIN
UPDATE Angajati
SET salariul=salariul*procent/100
WHERE id_angajat=20;
COMMIT;
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) comanda UPDATE este permisă într-un bloc PL/SQL
b) blocul realizează o operaţie de actualizare
c) comanda COMMIT nu este permisă într-un bloc PL/SQL
d) variabila procent trebuie iniţializată în cadrul secţiunii executabile, nu
în DECLARE
e) comanda COMMIT poate apărea în bloc, dar este independentă de acesta,
având efect şi asupra comenzilor din afara blocului.

52. Care din următoarele concepte din teoria relaţională şi teoria bazelor de date sunt
similare:
a) relaţie şi colecţie de date
b) linie şi câmp
c) atribut şi linie
d) atribut şi caracteristică
e) tuplu şi familie de caracteristici.

53. Un cursor implicit poate fi evaluat folosind atributele:


a) SQL%ROWCOUNT
b) SQL%FOUND
c) SQL%NOTFOUND

- 157 -
Baze de date. Limbajul PL/SQL

d) %TYPE
e) %ROWCOUNT

54. Care din următoarele caracteristici constituie reguli ale lui Codd pentru a defini
SGBDR:
a) independenţa fizică a datelor
b) independenţa logică a datelor
c) reprezentarea logică a datelor
d) garantarea accesului la date
e) restricţionarea accesului la date

55. Operatorii relaţionali de proiecţie, selecţie şi joncţiune sunt specifici:


a) algebrei booleene
b) transformării ortogonale
c) calculului relaţional
d) transformării de tip mapping
e) algebrei relaţionale

56. Specificaţi variantele ce pot fi obiective ale unui SGBD:


a) asigurarea integrităţii datelor
b) maximizarea timpului de acces
c) independenţa conceptuală a datelor
d) optimizarea structurii bazei de date
e) înlăturarea anomaliilor de ordin fizic.

57. Regulile lui Codd se referă la:


a) garantarea accesului la programe
b) independenţa fizică a datelor
c) restricţiile de integritate
d) independenţa conceptuală a datelor
e) actualizarea tabelelor virtuale.

58. Funcţii de bază ale unui SGBD sunt:


a) descrierea datelor
b) manipularea programelor
c) descrierea interfeţelor
d) utilizarea datelor
e) administrarea reţelei.

59. Un SGBD:
a) este un limbaj de regăsire
b) deţine întotdeauna SQL
c) este componenta software a unui sistem de baze de date
d) este o bază de date
e) asigură gestiunea tuturor fişierelor de pe un sistem de calcul.

60. Se dă următoarea secvenţă de comenzi:


CREATE OR REPLACE TRIGGER verifica_limita
BEFORE UPDATE OF limita_credit ON clienti
FOR EACH ROW
WHEN (NEW. limita_credit <OLD. limita_credit)

- 158 -
Baze de date. Limbajul PL/SQL

BEGIN
RAISE_APPLICATION_ERROR(-20202,'nu aveţi voie să micşoraţi limita de
credit!');
END;
/
Care afirmaţii sunt eronate:
a) în secvenţă se specifică evenimentul care declanşează trigger-ul
b) în secvenţă se specifică tabela asociată trigger-ului
c) în secvenţă se specifică de câte ori se execută trigger-ul
d) calificatorii OLD şi NEW trebuiau prefixaţi prin „:”
e) în secvenţă se specifică momentul declanşării trigger-ului.

61. Pentru excepţiile definite de utilizator este obligatoriu să:


a) să fie declarate
b) să fie tratate în secţiunea EXCEPTION
c) să fie invocate de utilizator
d) să fie invocate automat de serverul Oracle
e) să fie închise.

62. Se dă următoarea secvenţă de comenzi:


SET SERVEROUTPUT ON
DECLARE
CURSOR c IS SELECT nume_client FROM clienti;
r c%ROWTYPE;
BEGIN
OPEN c;
FETCH c INTO r;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('nume='||r.nume_client);
END;
/
Blocul PL/SQL afişează:
a) numele unui client
b) nu afişează nimic
c) un mesaj de eroare deoarece nu este corect declarată variabila r
d) un mesaj de eroare deoarece cursorul este închis înaintea afişării
e) un mesaj de eroare deoarece un se foloseşte o structură repetitivă.

63. Se dă următoarea secvenţă de comenzi:


SET SERVEROUTPUT ON
DECLARE
CURSOR c IS SELECT nume_client FROM clienti;
r c%ROWTYPE;
BEGIN
OPEN c;
FETCH c INTO r;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('nume='||c.nume_client);
END;
/
Blocul PL/SQL afişează:

- 159 -
Baze de date. Limbajul PL/SQL

a) numele unui client


b) nu afişează nimic
c) un mesaj de eroare deoarece nu este corect folosită variabila cursor
d) un mesaj de eroare deoarece cursorul este închis înaintea afişării
e) un mesaj de eroare deoarece un se foloseşte o structura repetitivă.

64. Obiectivele unui SGBD sunt:


a) asigurarea unei redundanţe minime şi controlate a datelor
b) asigurarea dependenţei datelor faţă de aplicaţii
c) asigurarea protecţiei datelor
d) asigurarea resurselor materiale
e) asigurarea partajabilităţii datelor.

65. SGBD Oracle 10g nu implementează modelul de date:


a) relaţional-reţea
b) ierarhic
c) reţea
d) relaţional
e) obiectual

66. Regulile lui Codd se referă la:


a) reprezentarea logică a datelor
b) independenţa fizică a utilizatorilor
c) restricţiile de integritate
d) independenţa conceptuală a datelor
e) prelucrarea datelor la nivel de bază.

67. Se consideră următoarea secvenţă de program:


DECLARE
CURSOR cursor1 IS SELECT id_angajat, nume FROM Angajati;
vid angajati.id_angajat%TYPE;
vnume CHAR (20);
BEGIN
OPEN cursor1;
FETCH cursor1 INTO vid,vnume;
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) lucrează cu un cursor explicit
b) lucrează cu un cursor implicit
c) returnează o eroare deoarece nu conţine ciclul LOOP…END LOOP.
d) returnează o eroare deoarece nu se închide explicit cursorul
e) se folosesc variabile record

68. Se consideră următorul bloc PL/SQL:


SET SERVEROUTPUT ON
DECLARE
v NUMBER(2);
BEGIN
v:=1000;
EXCEPTION

- 160 -
Baze de date. Limbajul PL/SQL

WHEN OTHERS THEN


DBMS_OUTPUT.PUT_LINE(‘Eroare’);
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) blocul declară o variabilă numerică
b) blocul se execută, dar nu afişează nimic
c) blocul nu se execută deoarece nu sunt instrucţiuni executabile
d) blocul afişează mesajul Eroare
e) blocul conţine erori de sintaxă

69. Se consideră tabela Angajaţi şi următorul bloc PL/SQL:


SET SERVEROUTPUT ON
CREATE OR REPLACE TRIGGER t_angajati
BEFORE DELETE ON Angajati
FOR EACH ROW
BEGIN
RAISE_APPLICATION_ERROR (-20100,'Operatie interzisa!');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(’S-a sters un angajat’);
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) blocul realizează un declanşator
b) blocul apelează o procedură
c) blocul şterge înregistrări din tabela Angajati
d) declanşatorul se execută pentru fiecare înregistrare care se şterge din
tabela Angajati
e) blocul declanşează o excepţie utilizator, dar nu împiedică ştergerea
înregistrărilor din tabela Angajaţi.

70. Se dă următoarea secvenţă PL/SQL:


DECLARE
p clienti%ROWTYPE;
CURSOR c2 IS SELECT nume_client, limita_credit FROM clienti WHERE
limita_credit > 1000
ORDER BY prenume_client DESC;
BEGIN
FOR p IN c2 LOOP
DBMS_OUTPUT.PUT_LINE('clientul '||p. nume_client ||' are limita de credit: '||p.
limita_credit);
EXIT WHEN c2%ROWCOUNT<=5;
END LOOP;
END;
/
Care dintre următoarele afirmaţii sunt corecte:
a) secvenţa utilizează un cursor explicit prin care se afişează toţi clienţii cu limita
de credit mai mare de 1000
b) secvenţa va afişa o eroare deoarece nu se poate ieşi cu exit dintr-un ciclu FOR
c) secvenţa afişează un top al primilor 5 clienţi cu limita de credit cea mai mare

- 161 -
Baze de date. Limbajul PL/SQL

d) afişează un singur client cu limita de credit aferentă


e) variabila p din secvenţă este de tip record.

71. Se dă următoarea secvenţă de comenzi:


CREATE OR REPLACE TRIGGER verifica_limita
BEFORE UPDATE OF limita_credit ON clienti
FOR EACH ROW
WHEN (:NEW. limita_credit <:OLD. limita_credit)
BEGIN
RAISE_APPLICATION_ERROR(-20202,'nu aveţi voie să micşoraţi limita de
credit!');
END;
/
Care afirmaţii sunt corecte:
a) în secvenţă se specifică evenimentul care declanşează trigger-ul
b) în secvenţă se specifică tabela asociată trigger-ului
c) în secvenţă se specifică de câte ori se execută trigger-ul
d) calificatorii OLD şi NEW nu trebuiau prefixaţi prin „:”
e) în secvenţă se specifică momentul declanşării trigger-ului.

72. Specificaţi variantele ce pot fi obiective ale unui SGBD:


a) asigurarea integrităţii datelor
b) maximizarea timpului de acces
c) independenţa conceptuală a datelor
d) optimizarea aplicaţiilor cu baze de date
e) înlăturarea anomaliilor de ordin fizic.

73. Se dă următoarea secvenţă de comenzi:


SET SERVEROUTPUT ON
DECLARE
CURSOR c IS SELECT nume_client FROM clienti;
r c%ROWTYPE;
BEGIN
OPEN c;
LOOP
FETCH c INTO r;
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('nume='||r.nume_client);
END;
/
Blocul PL/SQL afişează:
a) numele tuturor clienţilor
b) nu afişează nimic
c) un mesaj de eroare deoarece nu este corect declarată variabila r
d) un mesaj de eroare deoarece cursorul este închis înaintea afişării
e) numele ultimului client

- 162 -
Baze de date. Limbajul PL/SQL

RĂSPUNSURI ÎNTREBĂRI GRILĂ

1. a, b; 2. d; 3. a, d; 4. a, b, c, d, e; 5. a, d; 6. a, d, e; 7. a, b; 8. a, d, e; 9. a, c; 10. a, d, e; 11. c,

d; 12. a, c, d, e; 13. a, d; 14. a, c, d; 15. d; 16. d; 17. c; 18. b, d; 19. b, d; 20. a, b; 21. b, e; 22. a,

b, e; 23. a, b, c; 24. b, d; 25. a, c, d, e; 26. a, b; 27. e; 28. b, d; 29. a, b, e; 30. b; 31. a, b, d; 32.

a, c; 33. a, b, e; 34. a, c, e; 35. a, b, d, e; 36. a, b, c; 37. a, b, d; 38. a, b, c, d; 39. a; 40. b, c, d;

41. a, b, c, d; 42. c; 43. a, c, d; 44. b, c; 45. a; 46. a; 47. c; 48. a, b, d, e; 49. a, c, d, e; 50. d; 51.

a, b, e; 52. a, d, e; 53. a, b, c; 54. a, b, c, d; 55. e; 56. a; 57. b, c, e; 58. a, d; 59. c; 60. d; 61. a,

c; 62. a; 63. c; 64. a, c, e; 65. a, b, c, e; 66. a, c, e; 67. a; 68. a, d; 69. a, d, e; 70. e; 71. a, b, c, d,

e; 72. a; 73. e.

- 163 -
Baze de date. Limbajul PL/SQL

SUBIECTE DATE LA EXAMENUL DE ADMITERE


LA PROGRAMUL DE MASTERAT DE APROFUNDARE
INFORMATICĂ ECONOMICĂ
Sesiunea Septembrie 2008†

1. O relaţie este în forma normală 3 (FN3) dacă:


a) este în forma normală 2 (FN2) şi fiecare atribut non-cheie primară depinde în mod
netranzitiv de cheia primară a relaţiei
b) este în forma normală 1 (FN1) şi fiecare atribut cheie primară depinde tranzitiv de
atributele non-cheie primară
c) este în forma normală 2 (FN2) şi are dependenţe funcţionale complete
d) este în forma normală 1 (FN1) şi are dependenţe funcţionale incomplete
e) este în forma normală 2 (FN2) şi are cel puţin o dependenţă funcţională completă
între atributele non-cheie şi cheia primară a relaţiei

2. Normalizarea relaţiilor din cadrul bazelor de date relaţionale oferă posibilitatea:


a) reducerii complexităţii sistemului informatic
b) sporirii calităţii datelor de intrare
c) sporirii vitezei de răspuns la cererile utilizatorilor
d) înlăturării anomaliilor de actualizare
e) verificării corelaţiilor logice dintre atributele relaţiei

3. Specificaţi care dintre următoarele activităţi nu contribuie la asigurarea securităţii


bazei de date:
a) autorizarea şi controlul accesului la date
b) definirea unor tabele virtuale pentru grupuri de utilizatori
c) realizarea de proceduri speciale care sunt accesibile anumitor utilizatori
d) definirea restricţiilor de integritate
e) criptarea datelor

4. Specificaţi care dintre următorii operatori nu face parte din grupa operatorilor standard
din algebra relaţională:
a) selecţia
b) proiecţia
c) joncţiunea
d) produsul cartezian
e) cuantificatorul existenţial

5. Specificaţi care afirmaţie este una dintre funcţiile de bază ale unui sistem de gestiune a
bazelor de date (SGBD):
a) manipularea programelor
b) administrarea reţelei de calculatoare
c) proiectarea bazei de date
d) manipularea datelor
e) utilizarea limbajelor universale

Versiunea completă a setului de subiecte extras în sesiunea Septembrie 2008 se găseşte integral în
cartea: Admitere 2009 - Masterate de aprofundare editura ASE, 2009, ISBN: 978-606-505-138-6
Notă: Întrebările au un singur răspuns corect!

- 164 -
Baze de date. Limbajul PL/SQL

6. Specificaţi care dintre afirmaţiile următoare nu face parte din regulile lui Codd pentru
caracterizarea sistemelor de gestiune a bazelor de date relaţionale:
a) independenţa conceptuală a datelor
b) actualizarea tabelelor virtuale
c) independenţa fizică a datelor
d) distribuirea geografică a datelor
e) garantarea accesului la date

7. În vederea optimizării cererilor de regăsire, strategia cea mai bună pentru


transformarea expresiei relaţionale este:
a) joncţiunea înaintea proiecţiei
b) proiecţia înaintea selecţiei
c) selecţia înaintea proiecţiei
d) joncţiunea înaintea selecţiei
e) nu are importanţă ordinea

8. Care dintre următoarele afirmaţii ar putea fi considerată drept obiectiv al unui sistem
de gestiune a bazelor de date (SGBD):
a) asigurarea integrităţii datelor
b) maximizarea timpului de acces
c) independenţa conceptuală a datelor
d) creşterea complexităţii sistemului informatic
e) înlăturarea anomaliilor de ordin logic

9. Se consideră tabela: produse (codp number(3), denp varchar2(25), um varchar2(3))


Comanda SQL-Oracle:
SELECT DISTINCT codp, denp
FROM produse
WHERE codp > 100
ORDER BY denp;
implementează următorii operatori din algebra relaţională:
a) proiecţie şi joncţiune
b) selecţie şi intersecţie
c) selecţie şi reuniune
d) selecţie şi proiecţie
e) ordonare şi selecţie

10. Se consideră tabela: angajati (marca number(3), nume_angajat varchar2(35),


data_angajarii date, functie varchar2(15), salariu number(5), id_departament
number(3))
Care din următoarele variante de cereri SQL-Oracle va returna o valoare numerică:
a) SELECT SYSDATE + 600 / 24 FROM dual;
b) SELECT SYSDATE - data_angajarii FROM angajati;
c) SELECT ROUND(data_angajarii, 'DAY') FROM angajati;
d) SELECT SYSDATE - 7 FROM dual;
e) SELECT SYSDATE - 8/5 FROM dual;

11. Fie tabelele:


angajati (marca number(3), nume_angajat varchar2(35), data_angajarii date, functie
varchar2(15), salariu number(5), id_departament number(3))

- 165 -
Baze de date. Limbajul PL/SQL

şi
departamente ( id_departament number (3), denumire_dep varchar2(20) ),
Tabela angajati conţine informaţii şi despre angajaţi care nu aparţin unor
departamente, iar tabela departamente poate conţine şi departamente noi, care nu au angajaţi.
Se consideră următoarea comandă SQL:
SELECT a.nume_angajat, d.denumire_dep
FROM angajati a FULL OUTER JOIN departamente d
ON a.id_departament = d.id_departament;
Specificaţi care dintre comenzile următoare specifice SQL-Oracle afişează acelaşi
rezultat cu cea de mai sus:
a) SELECT a.nume_angajat, d.denumire_dep
FROM angajati a, departamente d
WHERE a.id_departament (+) = d.id_departament;
b) SELECT a.nume_angajat, d.denumire_dep
FROM angajati a, departamente d
WHERE a.id_departament = d.id_departament (+);
c) SELECT a.nume_angajat, d.denumire_dep
FROM angajati a, departamente d
WHERE a.id_departament (+) = d.id_departament (+);
d) SELECT a.nume_angajat, d.denumire_dep
FROM angajati a, departamente d
WHERE a.id_departament (+) = d.id_departament
UNION
SELECT a.nume_angajat, d.denumire_dep
FROM angajati a, departamente d
WHERE a.id_departament = d.id_departament (+);
e) SELECT a.nume_angajat, d.denumire_dep
FROM angajati a, departamente d;

12. Se consideră tabela: produse (codp number(3), denp varchar2(25), um varchar2(3))


Precizaţi care este efectul comenzii SQL-Oracle:
ALTER TABLE produse ADD (stoc number(10));
a) adăugarea unei noi linii la sfârsitul tabelei
b) adăugarea unei noi coloane în tabela
c) adăugarea unei noi linii dupa înregistrarea curentă
d) adăugarea unei noi linii la începutul tabelei
e) adăugarea tipului şi lungimii pentru coloana stoc existentă deja

13. Se consideră tabela: angajati (marca number(3), nume_angajat varchar2(35),


data_angajarii date, functie varchar2(15), salariu number(5), id_departament
number(3))
Să se precizeze care este efectul comenzii SQL-Oracle:
SELECT marca, nume_angajat, salariu
FROM angajati
WHERE functie IN
(SELECT functie FROM angajati WHERE nume_angajat = 'Vlad Vasile');
a) selectează informaţii despre angajaţii care au aceeaşi funcţie ca şi cea a angajatului
Vlad Vasile
b) selectează informaţii despre angajaţii care nu au aceeaşi funcţie ca şi cea a
angajatului Vlad Vasile
c) selectează informaţii despre toţi angajaţii firmei

- 166 -
Baze de date. Limbajul PL/SQL

d) selectează informaţii despre angajaţii care au acelaşi salariu ca şi cel al angajatului


Vlad Vasile
e) comanda este eronată

14. În SQL-Oracle, pentru a împărţi o tranzacţie în subtranzacţii se foloseşte comanda:


a) SET TRANSACTION
b) COMMIT
c) ROLLBACK
d) SAVEPOINT
e) nu se poate

15. În PL/SQL, un declanşator (trigger) este executat automat:


a) prin apel cu o instrucţiune CALL
b) prin denumirea sa
c) la apariţia unei declaraţii
d) la apariţia unui eveniment
e) la apariţia comenzii EXECUTE

16. Se consideră tabela: angajati (marca number(3), nume_angajat varchar2(35),


data_angajarii date, functie varchar2(15), salariu number(5), id_departament
number(3))
Blocul PL/SQL:
CREATE OR REPLACE
TRIGGER CRESC_COMISION
BEFORE INSERT OR UPDATE ON angajati
FOR EACH ROW
WHEN (new.functie LIKE 'economist')
BEGIN
IF INSERTING THEN
:NEW.salariu:=1000;
ELSIF :NEW.salariu <1000 THEN
RAISE_APPLICATION_ERROR (-20202, 'Un angajat nu poate avea salariul mai
mic de 1000 RON');
ELSE
:NEW.salariu:=:OLD.salariu * 1.15;
END IF;
END;
/
a) este eronat, deoarece variabilele NEW şi OLD nu sunt declarate
b) este eronat, deoarece excepţia -20202 nu este declarată şi tratată în zona de
excepţii
c) este eronat, deoarece variabila new.functia în secvenţa WHEN
(new.functia='economist') trebuie prefixată cu : (două puncte)
d) este corect şi modifică valoarea salariului în funcţie de tipul operaţiei şi/sau de
valoarea salariului anterior
e) este corect, dar niciodată nu se vor realiza modificări asupra valorilor
înregistrărilor pentru că întotdeauna se va declanşa eroarea -20202

17. Se consideră tabela: angajati (marca number(3), nume_angajat varchar2(35),


data_angajarii date, functie varchar2(15), salariu number(5), id_departament
number(3))

- 167 -
Baze de date. Limbajul PL/SQL

Precizaţi care este efectul execuţiei următorului bloc PL/SQL:


DECLARE
CURSOR c_angajat IS SELECT salariu FROM angajati;
v_salariu angajati.salariu%TYPE;
BEGIN
OPEN c_angajat;
FETCH c_angajat INTO v_salariu;
CLOSE c_angajat;
FETCH c_angajat INTO v_salariu;
END;
/
a) secvenţa de program este eronată şi se va invoca excepţia INVALID_CURSOR
b) înregistrarea corespunzătoare primului angajat va fi parcursă de 2 ori
c) vor fi parcurse înregistrările corespunzătoare primilor 2 angajaţi
d) va fi invocată excepţia TOO_MANY_ROWS
e) blocul PL/SQL se va executa cu succes

RĂSPUNSURI ÎNTREBĂRI

1. a; 2. d; 3. d; 4. e; 5. d; 6. a; 7. c; 8. a; 9. d; 10. b; 11. d; 12. b; 13. a; 14. d; 15. d; 16. d; 17. a

- 168 -
Baze de date. Limbajul PL/SQL

BIBLIOGRAFIE

[ORAPL] Oracle Database PL/SQL User’s Guide and Reference, Oracle Corporation

[LUBA08] Ion Lungu, Adela Bâra, Mihai Andronie - Administrarea bazelor de date, editura
ASE, Bucureşti, 2008, ISBN: 978-606-505-071-6

[VELU07] Manole Velicanu - Baze de date prin exemple, editura ASE, Bucureşti, 2007,
ISBN: 978-973-594-918-1

[LUNG05] Ion Lungu - Baze de date Oracle. Limbajul SQL, editura ASE, Bucureşti, 2005,
ISBN: 973-594-684-X

[VELU05] Manole Velicanu – Dicţionar explicativ al sistemelor de baze de date, editura


Economică, Bucureşti, 2005, ISBN: 709-114-0

[VELU03] Manole Velicanu, Ion Lungu, Mihaela Munteanu, Simona Ionescu – Sisteme de
baze de date – teorie şi practică, editura Petrion, Bucureşti, 2003, ISBN: 973-947-
070-X

[****] Suport electronic pentru seminarul de SGBD Oracle, CSIE-ASE, anul universitar
2008-2009: http://bd.ase.ro/

- 169 -
Baze de date. Limbajul PL/SQL

ANE X E

ANEXA 1 – Scriptul pentru crearea tabelelor bazei de date

spool ru.log

prompt
prompt Creating table REGIUNI
prompt ======================
prompt
create table REGIUNI
(
ID_REGIUNE NUMBER,
DENUMIRE_REGIUNE VARCHAR2(25)
)
;
alter table REGIUNI
add constraint ID_REGIUNE_PK primary key (ID_REGIUNE);
alter table REGIUNI
add constraint ID_REGIUNE_NN
check ("ID_REGIUNE" IS NOT NULL);

prompt
prompt Creating table TARI
prompt ===================
prompt
create table TARI
(
ID_TARA CHAR(2),
DENUMIRE_TARA VARCHAR2(40),
ID_REGIUNE NUMBER,
constraint TARA_C_ID_PK primary key (ID_TARA)
)
organization index;
alter table TARI
add constraint TARA_REG_FK foreign key (ID_REGIUNE)
references REGIUNI (ID_REGIUNE);
alter table TARI
add constraint ID_TARA_NN
check ("ID_TARA" IS NOT NULL);

prompt
prompt Creating table LOCATII
prompt ======================
prompt
create table LOCATII
(
ID_LOCATIE NUMBER(4) not null,
ADRESA VARCHAR2(40),
COD_POSTAL VARCHAR2(12),
ORAS VARCHAR2(30),
ZONA VARCHAR2(25),
ID_TARA CHAR(2)
)
;
alter table LOCATII
add constraint LOC_ID_PK primary key (ID_LOCATIE);

- 170 -
Baze de date. Limbajul PL/SQL

alter table LOCATII


add constraint LOC_C_ID_FK foreign key (ID_TARA)
references TARI (ID_TARA);
alter table LOCATII
add constraint LOC_ORAS_NN
check ("ORAS" IS NOT NULL);
create index LOC_ORAS_IX on LOCATII (ORAS);
create index LOC_TARA_IX on LOCATII (ID_TARA);
create index LOC_ZONA_IX on LOCATII (ZONA);

prompt
prompt Creating table DEPARTAMENTE
prompt ===========================
prompt
create table DEPARTAMENTE
(
ID_DEPARTAMENT NUMBER(4) not null,
DENUMIRE_DEPARTAMENT VARCHAR2(30),
ID_MANAGER NUMBER(6),
ID_LOCATIE NUMBER(4)
)
;
alter table DEPARTAMENTE
add constraint DEPT_ID_PK primary key (ID_DEPARTAMENT);
alter table DEPARTAMENTE
add constraint DEPT_LOC_FK foreign key (ID_LOCATIE)
references LOCATII (ID_LOCATIE);

alter table DEPARTAMENTE


add constraint DEPT_NAME_NN
check ("DENUMIRE_DEPARTAMENT" IS NOT NULL);
create index DEPT_LOCATION_IX on DEPARTAMENTE (ID_LOCATIE);

prompt
prompt Creating table FUNCTII
prompt ======================
prompt
create table FUNCTII
(
ID_FUNCTIE VARCHAR2(10) not null,
DENUMIRE_FUNCTIE VARCHAR2(35),
SALARIU_MIN NUMBER(6),
SALARIU_MAX NUMBER(6)
)
;
alter table FUNCTII
add constraint ID_FUNCTIE_PK primary key (ID_FUNCTIE);
alter table FUNCTII
add constraint DEN_FUNCTIE_NN
check ("DENUMIRE_FUNCTIE" IS NOT NULL);

prompt
prompt Creating table ANGAJATI
prompt =======================
prompt
create table ANGAJATI
(
ID_ANGAJAT NUMBER(6) not null,
PRENUME VARCHAR2(20),
NUME VARCHAR2(25),
EMAIL VARCHAR2(25),
TELEFON VARCHAR2(20),
DATA_ANGAJARE DATE,

- 171 -
Baze de date. Limbajul PL/SQL

ID_FUNCTIE VARCHAR2(10),
SALARIUL NUMBER(8,2),
COMISION NUMBER(2,2),
ID_MANAGER NUMBER(6),
ID_DEPARTAMENT NUMBER(4)
)
;
alter table ANGAJATI
add constraint ANG_ID_ANGAJAT_PK primary key (ID_ANGAJAT);
alter table ANGAJATI
add constraint ANG_EMAIL_UK unique (EMAIL);
alter table ANGAJATI
add constraint ANG_DEPT_FK foreign key (ID_DEPARTAMENT)
references DEPARTAMENTE (ID_DEPARTAMENT);
alter table ANGAJATI
add constraint ANG_FUNCTIE_FK foreign key (ID_FUNCTIE)
references FUNCTII (ID_FUNCTIE);
alter table ANGAJATI
add constraint ANG_MANAGER_FK foreign key (ID_MANAGER)
references ANGAJATI (ID_ANGAJAT);
alter table ANGAJATI
add constraint ANG_DATA_ANG_NN
check ("DATA_ANGAJARE" IS NOT NULL);
alter table ANGAJATI
add constraint ANG_EMAIL_NN
check ("EMAIL" IS NOT NULL);
alter table ANGAJATI
add constraint ANG_FUNCTIE_NN
check ("ID_FUNCTIE" IS NOT NULL);
alter table ANGAJATI
add constraint ANG_NUME_NN
check ("NUME" IS NOT NULL);
alter table ANGAJATI
add constraint ANG_SALARIUL_MIN
check (SALARIUL > 0);
create index ANG_DEPARTAMENT_IX on ANGAJATI (ID_DEPARTAMENT);
create index ANG_FUNCTIE_IX on ANGAJATI (ID_FUNCTIE);
create index ANG_MANAGER_IX on ANGAJATI (ID_MANAGER);
create index ANG_NUME_IX on ANGAJATI (NUME, PRENUME);

alter table DEPARTAMENTE


add constraint DEPT_MGR_FK foreign key (ID_MANAGER)
references ANGAJATI (ID_ANGAJAT)
disable;

prompt
prompt Creating table CLIENTI
prompt ======================
prompt
create table CLIENTI
(
ID_CLIENT NUMBER(6) not null,
PRENUME_CLIENT VARCHAR2(20),
NUME_CLIENT VARCHAR2(20),
TELEFON VARCHAR2(20),
LIMITA_CREDIT NUMBER(9,2),
EMAIL_CLIENT VARCHAR2(30),
DATA_NASTERE DATE,
STAREA_CIVILA VARCHAR2(20),
SEX VARCHAR2(1),
NIVEL_VENITURI VARCHAR2(20)
)
- 172 -
Baze de date. Limbajul PL/SQL

;
alter table CLIENTI
add constraint CLIENTI_ID_CLIENT_PK primary key (ID_CLIENT);
alter table CLIENTI
add constraint CLIENTI_LIMITA_CREDIT_MAX
check (LIMITA_CREDIT <= 5000);
alter table CLIENTI
add constraint CL_NUME_NN
check ("NUME_CLIENT" IS NOT NULL);
alter table CLIENTI
add constraint CL_PRENUME_NN
check ("PRENUME_CLIENT" IS NOT NULL);
create index CLIENTI_NUME_IX on CLIENTI (UPPER(NUME_CLIENT),
UPPER(PRENUME_CLIENT));

prompt
prompt Creating table COMENZI
prompt ======================
prompt
create table COMENZI
(
NR_COMANDA NUMBER(12) not null,
DATA TIMESTAMP(6) WITH LOCAL TIME ZONE,
MODALITATE VARCHAR2(8),
ID_CLIENT NUMBER(6),
STARE_COMANDA NUMBER(2),
ID_ANGAJAT NUMBER(6)
)
;
alter table COMENZI
add constraint COMENZI_NR_COMANDA_PK primary key (NR_COMANDA);
alter table COMENZI
add constraint COMENZI_ID_ANGAJAT_FK foreign key (ID_ANGAJAT)
references ANGAJATI (ID_ANGAJAT) on delete set null;
alter table COMENZI
add constraint COMENZI_ID_CLIENT_FK foreign key (ID_CLIENT)
references CLIENTI (ID_CLIENT) on delete set null;
alter table COMENZI
add constraint COMENZI_DATA_NN
check ("DATA" IS NOT NULL);
alter table COMENZI
add constraint COMENZI_ID_CLIENT_NN
check ("ID_CLIENT" IS NOT NULL);
alter table COMENZI
add constraint COMENZI_MOD_CK
check (MODALITATE in ('direct','online'));
create index COMENZI_DATA_IX on COMENZI (DATA);
create index COMENZI_ID_ANGAJAT_IX on COMENZI (ID_ANGAJAT);
create index COMENZI_ID_CLIENT_IX on COMENZI (ID_CLIENT);

prompt
prompt Creating table ISTORIC_FUNCTII
prompt ==============================
prompt
create table ISTORIC_FUNCTII
(
ID_ANGAJAT NUMBER(6),
DATA_INCEPUT DATE,
DATA_SFARSIT DATE,
ID_FUNCTIE VARCHAR2(10),
ID_DEPARTAMENT NUMBER(4)
)
;

- 173 -
Baze de date. Limbajul PL/SQL

alter table ISTORIC_FUNCTII


add constraint IST_ID_ANG_DATA_INC_PK primary key (ID_ANGAJAT,
DATA_INCEPUT);
alter table ISTORIC_FUNCTII
add constraint IST_ANG_FK foreign key (ID_ANGAJAT)
references ANGAJATI (ID_ANGAJAT);
alter table ISTORIC_FUNCTII
add constraint IST_DEPT_FK foreign key (ID_DEPARTAMENT)
references DEPARTAMENTE (ID_DEPARTAMENT);
alter table ISTORIC_FUNCTII
add constraint IST_FUNCTII_FK foreign key (ID_FUNCTIE)
references FUNCTII (ID_FUNCTIE);
alter table ISTORIC_FUNCTII
add constraint IST_DATA_INC_NN
check ("DATA_INCEPUT" IS NOT NULL);
alter table ISTORIC_FUNCTII
add constraint IST_DATA_INTERVAL
check (DATA_SFARSIT > DATA_INCEPUT);
alter table ISTORIC_FUNCTII
add constraint IST_DATA_SF_NN
check ("DATA_SFARSIT" IS NOT NULL);
alter table ISTORIC_FUNCTII
add constraint IST_ID_ANG_NN
check ("ID_ANGAJAT" IS NOT NULL);
alter table ISTORIC_FUNCTII
add constraint IST_ID_FUNCTIE_NN
check ("ID_FUNCTIE" IS NOT NULL);
create index IST_ANGAJAT_IX on ISTORIC_FUNCTII (ID_ANGAJAT);
create index IST_DEPARTAMENT_IX on ISTORIC_FUNCTII (ID_DEPARTAMENT);
create index IST_FUNCTIE_IX on ISTORIC_FUNCTII (ID_FUNCTIE);

prompt
prompt Creating table PRODUSE
prompt ======================
prompt
create table PRODUSE
(
ID_PRODUS NUMBER(6) not null,
DENUMIRE_PRODUS VARCHAR2(50),
DESCRIERE VARCHAR2(2000),
CATEGORIE VARCHAR2(40),
PRET_LISTA NUMBER(8,2),
PRET_MIN NUMBER(8,2)
)
;
alter table PRODUSE
add constraint PRODUSE_ID_PRODUS_PK primary key (ID_PRODUS);

prompt
prompt Creating table RAND_COMENZI
prompt ===========================
prompt
create table RAND_COMENZI
(
NR_COMANDA NUMBER(12) not null,
ID_PRODUS NUMBER(6) not null,
PRET NUMBER(8,2),
CANTITATE NUMBER(8)
)
;
alter table RAND_COMENZI
add constraint PROD_COM_PK primary key (NR_COMANDA, ID_PRODUS);
alter table RAND_COMENZI

- 174 -
Baze de date. Limbajul PL/SQL

add constraint PROD_COM_ID_PRODUS_FK foreign key (ID_PRODUS)


references PRODUSE (ID_PRODUS);
alter table RAND_COMENZI
add constraint PROD_COM_NR_COMANDA_FK foreign key (NR_COMANDA)
references COMENZI (NR_COMANDA) on delete cascade;
alter table RAND_COMENZI
add constraint PROD_COM_CANTITATE_CK
check (CANTITATE>=0);
alter table RAND_COMENZI
add constraint PROD_COM_PRET_CK
check (PRET>=0);
create index PROD_COM_ID_PRODUS_IX on RAND_COMENZI (ID_PRODUS);
create index PROD_COM_NR_COMANDA_IX on RAND_COMENZI (NR_COMANDA);

spool off

- 175 -
Baze de date. Limbajul PL/SQL

ANEXA 2 – Schema conceptuală a bazei de date utilizată în exemple


Baze de date. Limbajul PL/SQL

ANEXA 3 – Lista excepţiilor predefinite Oracle

Exception ORA Error SQLCODE Raise When ...

Un program încearcă să asigneze


ACCESS_INTO_NULL 06530 -6530 valori atributelor unui obiect
neiniţializat

Nicio variantă a unei clauze


CASE_NOT_FOUND 06592 -6592 CASE nu este selectată şi nu
există clauza ELSE.

Un program încearcă să aplice o


metodă, alta decât EXISTS într-
o tabelă imbricată sau varray,
sau programul încearcă să
COLLECTION_IS_NULL 06531 -6531
asigneze valori elementelor
unei tabele imbricate
neiniţializată sau a unui
varray neinitializat.

Un program încearcă să deschidă


un cursor deja deschis. Un
cursor trebuie închis înainte
să poată fi redeschis. Un
CURSOR_ALREADY_OPEN 06511 -6511
cursor parcurs cu FOR este
automat deschis şi nu mai
trebuie deschis în interiorul
buclei.

Un program încearcă să stocheze


valori duplicate într-o
coloană/grup de coloane care
DUP_VAL_ON_INDEX 00001 -1
sunt restricţionate printr-un
index pentru asigurarea
unicităţii.

Un program încercă o operaţie


pe un cursor care nu este
INVALID_CURSOR 01001 -1001 permisă, cum ar fi închiderea
unui cursor care nu a fost
deschis.

Într-o instrucţiune SQL,


conversia din şir de caractere
în numeric nu este posibilă. In
instrucţiuni procedurale apare
INVALID_NUMBER 01722 -1722 excepţia VALUE_ERROR. Această
excepţie mai poate apărea când
clauza LIMIT dintr-o
instrucţiune FETCH nu
returnează o valoare pozitivă.

- 177 -
Baze de date. Limbajul PL/SQL

Un program încearcă să se
LOGIN_DENIED 01017 -1017 autentifice cu un utilizator
şi/sau parola greşită.

Un SELECT INTO returneză nicio


înregistrare sau un program
face referire la un element
şters dintr-o tabelă imbricată
sau neiniţializat într-o tabelă
NO_DATA_FOUND 01403 +100 INDEX-BY. Deoarece această
excepţie este folosită intern
de unele funcţii SQL nu trebuie
pusă bază pe propagarea ei dacă
apare într-o funcţie folosită
într-o interogare.

Un program înceară un apel la


NOT_LOGGED_ON 01012 -1012 baza de date fără a fi
conectat.

PROGRAM_ERROR 06501 -6501 PL/SQL are o problema internă.

Variabila de tip rând şi cea de


tip cursor nu se potrivesc.
Când o variabilă cursor este
ROWTYPE_MISMATCH 06504 -6504 returnată unui subprogram,
tipul variabilelor returnate şi
a parametrilor formali trebuie
să fie compatibile.

Un program încearcă să cheme o


metodă MEMBER dar instanţa
tipului obiectului nu a fost
SELF_IS_NULL 30625 -30625 iniţializat. Parametrul SELF
referă obiectul şi este mereu
primul parametru trimis unei
metode MEMBER.

PL/SQL a rămas fără memorie sau


STORAGE_ERROR 06500 -6500
memoria este coruptă.

Un program face referire la o


tabelă imbricată sau element
SUBSCRIPT_BEYOND_COUNT 06533 -6533 varray folosind un index mai
mare decât numărul de elemente
din colecţie.

Un program face referire la o


tabelă imbricată sau la un
SUBSCRIPT_OUTSIDE_LIMIT 06532 -6532
varray folosind un număr în
afara limitelor admise.

Conversia unui string în ROWID


universal eşuează deoarece
SYS_INVALID_ROWID 01410 -1410
şirul nu reprezintă un ROWID
valid.

TIMEOUT_ON_RESOURCE 00051 -51


A apărut un time-out ca urmare

- 178 -
Baze de date. Limbajul PL/SQL

a aşteptării pentru eliberarea


unei resurse.

O instrucţiune SELECT INTO


TOO_MANY_ROWS 01422 -1422
returnează mai mult de un rând.

A apărut o eroare datorită unei


operaţii aritmetrice, de
conversie sau de trunchiere.
Spre exemplu, când programul
selectează o coloană într-o
variabilă caracter, dacă
valoarea este este mai mare
decât lungimea variabilei,
VALUE_ERROR 06502 -6502 PL/SQL va întrerupe asignarea
şi va ridica VALUE_ERROR. In
intrucţiuni procedurale,
VALUE_ERROR este ridicată dacă
nu se poate realiza conversia
unui şi de caractere într-o
variabilă numerică (în cazul
unei comenzi SQL, apare
excepţia INVALID_NUMBER).

Un program încearcă să împartă


ZERO_DIVIDE 01476 -1476
un număr la zero.

- 179 -

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