Sunteți pe pagina 1din 133

PROGRAMARE N PL/SQL

1. Concepte generale
Procedural Language/Structured Query Language (PL/SQL) este extensia procedural a limbajului SQL. PL/SQL este un limbaj de programare complex care asigur accesarea datelor unei baze de date relaionale orientate pe obiecte i permite gruparea unei mulimi de comenzi ntr-un bloc unic de tratare a datelor. Un program este format din unul sau mai multe blocuri care pot conine blocuri imbricate. Fiecare bloc logic corespunde unei probleme sau subprobleme de rezolvat. PL/SQL include att instruciuni SQL pentru prelucrarea datelor i gestiunea tranzaciilor, ct i instruciuni proprii. Limbajul mbin construciile procedurale ale unui limbaj LG3 cu puterea i flexibilitatea lui SQL (LG4). Aceast combinaie a generat un limbaj puternic pentru modelarea aplicaiilor complexe. PL/SQL extinde SQL prin construcii specifice limbajelor procedurale (definirea variabilelor, declararea tipurilor, utilizarea structurilor de control, implementarea subprogramelor, introducerea tipurilor obiect i a metodelor etc.). PL/SQL ofer posibiliti moderne de tratare a informaiei: ncapsularea datelor, analiza special a erorilor, mascarea informaiei, orientarea pe obiecte. Posibilitile lui SQL sunt folosite pentru un acces rafinat la date, iar facilitile oferite de PL/SQL, pentru fluxul controlului procesrii datelor. Dintre funcionalitile care determin utilizarea frecvent a limbajului PL/SQL, se remarc urmtoarele: integrarea comenzilor SQL de baz; integrarea cu server-ul Oracle i cu utilitarele Oracle; oferirea unui suport pentru programarea orientat pe obiecte;

248

PROGRAMARE AVANSAT N ORACLE9i

asigurarea securitii informaiei; definirea i gestiunea blocurilor de instruciuni; gestiunea variabilelor, constantelor i cursoarelor; modularizarea programelor (subprograme, pachete); implementarea i utilizarea declanatorilor; utilizarea structurilor de control fundamentale; detectarea i gestiunea erorilor de execuie, a situaiilor excepionale; dezvoltarea de aplicaii Web.

PL/SQL este o tehnologie utilizat de server-ul Oracle i de anumite utilitare Oracle. Blocurile PL/SQL sunt transmise unui motor PL/SQL i procesate (compilate i executate) de acesta. Motorul PL/SQL se poate afla pe server-ul Oracle sau ntr-un utilitar, iar utilizarea sa depinde de locul din care se invoc PL/SQL. Multe utilitare Oracle au propriul lor motor PL/SQL care este independent de motorul prezent pe server-ul Oracle. Blocurile PL/SQL pot fi executate pe staia client fr interaciune cu server-ul sau n ntregime pe server. Atunci cnd blocurile PL/SQL sunt referite dintr-un program PRO*, din SQL*Plus sau de ctre Server Manager, motorul PL/SQL de pe server-ul Oracle proceseaz aceste blocuri. Acesta descompune blocul n instruciuni SQL i le trimite executorului de instruciuni SQL (SQL Statement Executor) de pe server-ul Oracle. Fr PL/SQL, instruciunile SQL ar fi procesate separat, fiecare la un moment dat, fiecare implicnd un apel la server-ul Oracle. Restul comenzilor (procedurale) sunt procesate de ctre executorul instruciunilor procedurale (PSE Procedural Statement Executor) aflat n motorul PL/SQL. PSE proceseaz datele locale aplicaiei, reducndu-se astfel activitatea de transfer spre server-ul Oracle i numrul de cursoare deschise. n felul acesta, este necesar un singur transfer pentru a trimite blocul, din aplicaie ctre server. O aplicaie de baze de date poate fi structurat n trei pri: interfaa utilizator (utilizatorul introduce anumite informaii i obine nite rezultate n urma executrii aplicaiei); aplicaia logic efectiv; baza de date. Exist dou arhitecturi pentru proiectarea unei aplicaii de baze de date: modelul clasic client/server (two-tier) i modelul multitier (three-tier). Multe dintre aplicaiile de baze de date sunt construite folosind arhitectura tradiional client/server descris anterior pentru PL/SQL. Modelul este caracterizat de cele dou componente, client i server. Client-ul asigur interfaa cu utilizatorul, logica aplicaiilor i prelucrrii datelor. Server-ul se ocup cu validarea datelor i accesarea bazei de date. De remarcat caracteristica fundamental a modelului, i anume faptul c aplicaia comunic direct cu server-ul. Exist un motor PL/SQL pe server, iar n anumite cazuri i pe client. Dac motorul PL/SQL este pe server, atunci aplicaia (ce poate fi scris n Pro*C/C++, JDBC, OCI sau n alte limbaje) care rezid pe client trimite cereri la un server de date. Cererile sunt rezolvate utiliznd SQL. Diferite cereri SQL pot fi grupate ntr-un bloc PL/SQL i trimise server-ului ca o singur entitate, genernd

Programare n PL/SQL

249

diminuarea traficului i creterea rapiditii aplicaiei. Se consider un scenariu n care exist dou motoare PL/SQL, unul pe staia client (local) i unul pe server (de exemplu, un declanator se execut pe staia client i apeleaz un subprogram stocat n baza de date). n acest caz, blocurile anonime sunt trimise motorului PL/SQL de pe staia client, care proceseaz local comenzile procedurale. Comenzile neprocedurale din interiorul blocului sunt trimise executorului de instruciuni SQL de pe server. De asemenea, apelurile procedurilor care sunt stocate pe server sunt trimise pentru procesare tot motorului de pe server. Un mecanism similar este folosit pentru comunicarea dintre motoare PL/SQL aflate pe server-e diferite. n acest caz, obiectele PL/SQL din diferite motoare pot, ns, s depind unul de altul. n modelul three-tier, aplicaia de baze de date este scindat n trei pri separate ce corespund interfeei utilizator, logicii aplicaiei i bazei de date. Acestor trei straturi le corespund: Client Browser, Application Server (Oracle Internet Application Server iAS) i Oracle Database Server. Client-ul (cel mai frecvent un browser Internet) apeleaz subprograme care genereaz ca rezultat pagini HTML, iar Application Server proceseaz solicitrile, manevrnd logica aplicaiilor i prelucrrii datelor. n versiunea Oracle9i, aplicaiile Forms i Reports se execut numai ca parte a unui model three-tier (Web - Server). De exemplu, Oracle Web Forms permite desfurarea unei aplicaii client/server ntr-un mediu multitier Internet, fr a modifica nici o linie de cod. n general, comenzile SQL i PL/SQL sunt trimise server-ului pentru a fi executate. Pentru a realiza acest lucru, trebuie stabilit conectarea la baza de date. Aceasta presupune ca baza de date s autentifice utilizatorul (identificator i parol). PL/SQL nu conine o sintax special pentru realizarea conectrii la baza de date. Conectarea poate fi realizat de alte componente ale sistemului (de exemplu, SQL*Plus). PL/SQL are un rol important att la nivelul server-ului Oracle (prin subprograme stocate, declanatori i pachete), ct i la nivelul utilitarelor Oracle (de exemplu, Developer Suite componenta declanatori). Partea procedural este att la nivel de client, ct i la nivel de server. Cererile, ns, nu se execut pe staii client, ci numai pe server. Cu toate c au aceeai structur general, procedurile i funciile declarate ca parte a unei aplicaii Developer Suite sunt diferite de cele stocate n baza de date. Subprogramele stocate sunt obiecte depuse n dicionarul datelor, putnd fi accesate de un numr arbitrar de aplicaii, inclusiv de aplicaii Developer Suite. PL/SQL constituie fundamentul pentru realizarea diverselor aplicaii de baze de date:

fiiere SQL*Plus (script file); proceduri i funcii stocate (stored procedure i stored function) o procedur sau o funcie stocat este un subprogram PL/SQL care poate fi invocat de o aplicaie client, un declanator baz de date sau un

250

PROGRAMARE AVANSAT N ORACLE9i

declanator aplicaie; pachete (package) un pachet este un bloc PL/SQL care ncapsuleaz ntr-o unitate logic, n baza de date, o mulime de proceduri, funcii, variabile, constante, tipuri, cursoare i excepii; declanatori baz de date (database trigger) un declanator la nivel de baz de date este un bloc PL/SQL care se execut la apariia unui eveniment (modificarea unui tabel al bazei, modificarea unei vizualizri, anumite aciuni ale sistemului sau ale utilizatorului); declanatori aplicaie (application trigger) un declanator la nivel de aplicaie este un bloc PL/SQL care se execut n urma unui eveniment provocat de sistem. Aplicaiile Oracle (Forms Developer i Reports Developer) sunt dotate cu motoare PL/SQL care permit construirea acestor declanatori.

Referitor la portabilitatea limbajului, pot fi remarcate dou aspecte. Aplicaiile PL/SQL se pot executa pe orice platform sau sistem de operare pe care poate fi executat Oracle. PL/SQL permite transfer de cod ntre server-ul Oracle i aplicaii. Este posibil scrierea de pachete program portabile i crearea de biblioteci utilizabile n diferite situaii, de diverse aplicaii.

2. Ce aduce nou Oracle9i?


Pentru Oracle8, cele mai spectaculoase realizri erau legate de introducerea tipurilor obiect i a metodelor, definirea tipului colecie (tablouri imbricate i vectori), definirea i accesarea rutinelor externe, utilizarea tipurilor LOB. Dintre performanele versiunii Oracle8i se pot aminti: rutine externe Java, parametri NOCOPY, tranzacii autonome, operaii bulk bind, SQL dinamic nativ. Dintre noutile (referitoare la PL/SQL) oferite de Oracle9i se remarc: posibilitatea ca PL/SQL s suporte sintaxa complet a comenzilor SQL; introducerea unor noi tipuri de date n SQL i PL/SQL (TIMESTAMP, INTERVAL, colecii pe mai multe niveluri); introducerea de comenzi i expresii CASE; definirea expresiilor cursor; introducerea ierarhiilor de tipuri i subtipuri; definirea funciilor tabel; introducerea de pachete predefinite care ofer funcionaliti speciale legate de noile caracteristici ale versiunii; compilarea nativ a codului PL/SQL; extinderea posibilitilor oferite de structura bulk bind i mbinarea acestei structuri cu facilitile oferite de SQL dinamic; scrierea rapid de aplicaii C++ care au acces la un server Oracle,

Programare n PL/SQL

251

folosind o interfa API (Application Programming Interface), numit Oracle C++ Call Interface (OCCI); depunerea i executarea claselor Java n baza de date; dezvoltarea de aplicaii Web.

Procedurile PL/SQL se execut mai rapid prin compilarea lor ntr-un cod nativ. Ele sunt translatate n cod C, compilate cu ajutorul unui compilator C i apoi preluate automat n procese Oracle. Aceast tehnic, care nu impune restaurarea bazei de date, poate fi utilizat pentru procedurile i pachetele Oracle. Performanele oferite de sistem pentru dezvoltarea aplicaiilor sunt legate de implementarea urmtoarelor faciliti: SQL dinamic, structura bulk bind, argumentul NOCOPY, clauza RETURNING, rutine externe, tipuri obiect i colecii, compilarea codului PL/SQL pentru execuie nativ. Aceste posibiliti vor fi analizate n urmtoarele capitole. Sistemul Oracle furnizeaz soluii (interfee la nivel de client i server, utilitare, JVM integrat cu server-ul Oracle9i) pentru dezvoltatorii de aplicaii n vederea crerii, gestionrii i exploatrii aplicaiilor Java. Metodele Java stocate pot fi apelate din pachete PL/SQL, iar procedurile PL/SQL existente pot fi invocate din proceduri Java. Datele SQL pot fi accesate prin dou interfee (API): JDBC i SQLJ. Astfel: pentru a invoca o metod Java din SQL este nevoie de interfaa Java Stored Procedures; pentru a invoca, n mod dinamic, comenzi SQL complexe este folosit interfaa JDBC; pentru a utiliza comenzi SQL statice, simple (referitoare la un tabel ale crui coloane sunt cunoscute) referitor la o clas Java este folosit SQLJ. PL/SQL ofer faciliti care permit utilizarea Web-ului de ctre baza de date i d posibilitatea ca datele din baza de date s fie accesibile interactiv diferiilor utilizatori Intranet. Aplicaiile Web scrise n PL/SQL sunt de fapt proceduri stocate care interacioneaz, printr-un protocol HTTP, cu un browser Web. n dezvoltarea de aplicaii Web cu PL/SQL se impun cteva probleme. Cum se genereaz un rezultat HTML din PL/SQL? Cum se pot trimite parametri unei aplicaii Web? Cum se pot executa operaii Web (trimiterea unui e-mail, gsirea adresei unei gazde etc.) cu ajutorul procedurilor stocate PL/SQL? Cum se poate include cod PL/SQL n pagini Web? Sistemul furnizeaz o clas de pachete predefinite care uureaz dezvoltarea aplicaiilor Web. De exemplu, pentru a trimite un e-mail dintr-un program PL/SQL sau dintr-o procedur stocat se utilizeaz pachetul UTL_SMTP. De asemenea, pentru a gsi numele unei gazde locale sau distante a crei adres este cunoscut sau pentru a gsi adresa unei gazde creia i se cunoate numele poate fi utilizat pachetul UTL_INADDR. Pachetele pot fi folosite mpreun cu Oracle Internet Application Server i WebDB. n felul acesta se poate formata rezultatul unei cereri ntr-un tabel

252

PROGRAMARE AVANSAT N ORACLE9i

HTML, se pot combina operaii Web tipice ntr-un program PL/SQL etc. PL/SQL Server Pages (PSP) permite crearea de pagini Web dinamice, prin care se pot executa operaii asupra bazei de date i include rezultatele unei interogri n pagini Web, n mod dinamic. Folosind elemente speciale, numite tag-uri, se pot insera script-uri PL/SQL n codul surs HTML. Aceste script-uri se execut atunci cnd paginile sunt solicitate de clieni Web. Script-ul permite accesarea unor parametri, filtrarea sau reactualizarea bazei de date. Pentru a include codul PL/SQL n pagini Web trebuie: aleas o configuraie software; scris cod pentru PSP (prin care se creeaz pagini Web ce permit operaii asupra bazei de date); ncrcate fiierele PSP n baza de date (ca proceduri stocate) i executate. O aplicaie Web PL/SQL poate s accepte date n format XML sau s furnizeze rezultate n acest format. Documentul XML poate fi procesat ca un document compus (depus ntr-un LOB) sau ca un document descompus (fragmente de documente) depus n tabele relaionale.

3. Controlul execuiei unui bloc PL/SQL


PL/SQL este un limbaj cu structur de bloc, adic programele sunt compuse din blocuri care pot fi complet separate sau imbricate. Structura unui bloc poate fi obinut combinnd subprograme, pachete, blocuri imbricate. Blocurile pot fi folosite n utilitarele Oracle. Pentru modularizarea unui program este necesar: gruparea logic a instruciunilor n blocuri; imbricarea de subblocuri n blocuri mai mari; descompunerea unei probleme complexe ntr-o mulime de module logice i implementarea acestora cu ajutorul blocurilor; plasarea n biblioteci a codului PL/SQL reutilizabil, de unde poate fi folosit de aplicaii; depunerea codului ntr-un server Oracle, de unde este accesibil oricrei aplicaii care interacioneaz cu baza de date Oracle. Un program PL/SQL poate cuprinde unul sau mai multe blocuri. Un bloc poate fi anonim sau neanonim. Blocurile anonime sunt blocuri PL/SQL fr nume, care sunt construite dinamic i sunt executate o singur dat. Acest tip de bloc nu are argumente i nu returneaz un rezultat. Ele sunt declarate ntr-un punct al aplicaiei, unde vor fi executate (trimise motorului PL/SQL). n blocurile anonime pot fi declarate proceduri i funcii PL/SQL.

Programare n PL/SQL

253

Blocurile anonime pot s apar ntr-un program ce lucreaz cu precompilator sau n SQL*Plus. De obicei, blocurile anonime sunt plasate ntr-un fiier, iar apoi fiierul este executat din SQL*Plus. De asemenea, declanatorii din componentele Developer Suite constau din astfel de blocuri. Blocurile neanonime sunt fie blocuri cu nume (etichetate) construite static sau dinamic i executate o singur dat, fie subprograme, pachete sau declanatori. Subprogramele sunt proceduri sau funcii depuse n baza de date. Aceste blocuri sunt executate de mai multe ori i, n general, nu mai sunt modificate dup ce au fost construite. Procedurile i funciile stocate sunt depuse pe server-ul Oracle, accept parametri i pot fi apelate prin nume. Procedurile i funciile aplicaie sunt depuse ntr-o aplicaie Developer Suite sau ntr-o bibliotec. Pachetele (stocate sau aplicaie) sunt blocuri neanonime care grupeaz proceduri, funcii, cursoare, tipuri, constante, variabile ntr-o unitate logic, n baza de date. Declanatorii sunt blocuri PL/SQL neanonime depuse n baza de date, care pot fi asociai bazei, iar n acest caz sunt executai implicit ori de cte ori apare un anumit eveniment declanator (de exemplu, instruciuni INSERT, UPDATE sau DELETE ce se execut asupra unui tabel al bazei de date) sau pot fi asociai unei aplicaii (de exemplu, declanator SQL*Forms), ceea ce presupune c se execut automat, n funcie de anumite condiii sistem.

Structura unui bloc PL/SQL


Un bloc PL/SQL este compus din trei seciuni distincte. Seciunea declarativ (opional) conine declaraii pentru toate variabilele, constantele, cursoarele i erorile definite de utilizator la care se face referin n seciunea executabil sau chiar n cea declarativ. De asemenea, pot fi declarate subprograme locale care sunt vizibile doar n blocul respectiv. Seciunea executabil conine instruciuni neprocedurale SQL pentru prelucrarea datelor din baza de date i instruciuni PL/SQL pentru prelucrarea datelor n cadrul blocului. Seciunea pentru tratarea erorilor (opional) specific aciunile ce vor fi efectuate atunci cnd n execuia blocului apar erori sau condiii anormale. Blocul PL/SQL are urmtoarea structur general: [<<nume_bloc>>] [DECLARE instruciuni de declarare] BEGIN instruciuni executabile (SQL sau PL/SQL) [EXCEPTION tratarea erorilor] END [nume_bloc]; Dac blocul PL/SQL este executat fr erori, invariant va aprea mesajul:

254

PROGRAMARE AVANSAT N ORACLE9i

PL/SQL procedure successfully completed

Compatibilitate SQL
Din punct de vedere al compatibilitii dintre PL/SQL i SQL, se remarc urmtoarele reguli de baz: PL/SQL furnizeaz toate comenzile LMD ale lui SQL, comanda SELECT cu clauza INTO, comenzile LCD, funciile, pseudocoloanele i operatorii SQL; PL/SQL nu furnizeaz comenzile LDD. Totui, n ultimele sale versiuni, Oracle permite folosirea dinamic a comenzilor SQL, utiliznd tehnica oferit de SQL dinamic. n felul acesta, orice comand SQL (inclusiv comand LDD) poate s fie utilizat n PL/SQL. Majoritatea funciilor SQL sunt disponibile n PL/SQL. Exist ns funcii specifice PL/SQL, cum sunt funciile SQLCODE i SQLERRM. De asemenea, exist funcii SQL care nu sunt disponibile n instruciuni procedurale (DECODE, funciile grup), dar care sunt disponibile n instruciunile SQL dintr-un bloc PL/SQL. SQL nu poate folosi funcii sau atribute specifice PL/SQL. Funciile grup trebuie folosite cu atenie, deoarece clauza GROUP BY nu are sens s apar n instruciunea SELECT INTO. Oracle9i introduce clauza OVER, care permite ca funcia grup creia i este asociat s fie considerat o funcie analitic (poate returna mai multe linii pentru fiecare grup). Urmtoarele funcii SQL nu sunt permise n PL/SQL: WIDTH_BUCKET, BIN_TO_NUM, COMPOSE, DECOMPOSE, TO_LOB, DECODE, DUMP, EXISTSNODE, TREAT, NULLIF, SYS_CONNECT_BY_PATH, SYS_DBURIGEN, EXTRACT.

Instruciuni PL/SQL
Orice program poate fi scris utiliznd structuri de control de baz care sunt combinate n diferite moduri pentru rezolvarea problemei propuse. PL/SQL dispune de comenzi ce permit controlul execuiei unui bloc. Instruciunile limbajului pot fi: iterative (LOOP, WHILE, FOR), de atribuire (:=), condiionale (IF, CASE), de salt (GOTO, EXIT) i instruciunea vid (NULL). Observaii:

Comentariile sunt ignorate de compilatorul PL/SQL. Exist comentarii pe o singur linie, prefixate de simbolurile --, care ncep n orice punct al liniei i se termin la sfritul acesteia. De asemenea, exist comentarii pe mai multe linii, care sunt delimitate de simbolurile /* i */. Nu se admit comentarii imbricate. Caracterul ; este separator pentru instruciuni. Att operatorii din PL/SQL, ct i ordinea de execuie a acestora, sunt identici cu cei din SQL. n PL/SQL este introdus un nou operator (**) pentru ridicare la putere. Un identificator este vizibil n blocul n care este declarat i n toate

Programare n PL/SQL

255

subblocurile, procedurile i funciile imbricate n acesta. Dac blocul nu gsete identificatorul declarat local, atunci l caut n seciunea declarativ a blocurilor care includ blocul respectiv i niciodat nu caut n blocurile ncuibrite n acesta. Comenzile SQL*Plus nu pot s apar ntr-un bloc PL/SQL. n comanda SELECT trebuie specificate variabilele care recupereaz rezultatul aciunii acestei comenzi. n clauza INTO, care este obligatorie, pot fi folosite variabile PL/SQL sau variabile de legtur. Referirea la o variabil de legtur se face prin prefixarea acesteia cu simbolul :. Cererea dintr-o comand SELECT trebuie s returneze o singur linie drept rezultat. Atunci cnd comanda SELECT ntoarce mai multe linii, apare eroarea TOO_MANY_ROWS, iar n cazul n care comanda nu gsete date se genereaz eroarea NO_DATA_FOUND. Un bloc PL/SQL nu este o unitate tranzacional. ntr-un bloc pot fi mai multe tranzacii sau blocul poate face parte dintr-o tranzacie. Aciunile COMMIT, SAVEPOINT i ROLLBACK sunt independente de blocuri, dar instruciunile asociate acestor aciuni pot fi folosite ntr-un bloc. PL/SQL nu suport comenzile GRANT i REVOKE, utilizarea lor fiind posibil doar prin SQL dinamic.

Fluxul secvenial de execuie a comenzilor unui program PL/SQL poate fi modificat cu ajutorul structurilor de control: IF, CASE, LOOP, FOR, WHILE, GOTO, EXIT.

Instruciunea de atribuire
Instruciunea de atribuire se realizeaz cu ajutorul operatorului de asignare (:=) i are forma general clasic (variabila := expresie). Comanda respect proprietile instruciunii de atribuire din clasa LG3. De remarcat c nu poate fi asignat valoarea null unei variabile care a fost declarat NOT NULL. Exemplu: Urmtorul exemplu prezint modul n care acioneaz instruciunea de atribuire n cazul unor tipuri de date particulare.
DECLARE alfa INTERVAL YEAR TO MONTH; BEGIN alfa := INTERVAL '200-7' YEAR TO MONTH; -- alfa ia valoarea 200 de ani si 7 luni alfa := INTERVAL '200' YEAR; -- pot fi specificati numai anii alfa := INTERVAL '7' MONTH; -- pot fi specificate numai lunile alfa := '200-7'; -- conversie implicita din caracter END;

256 Instruciunea IF

PROGRAMARE AVANSAT N ORACLE9i

Un program PL/SQL poate executa diferite poriuni de cod, n funcie de rezultatul unui test (predicat). Instruciunile care realizeaz acest lucru sunt cele condiionale (IF, CASE). Structura instruciunii IF n PL/SQL este similar instruciunii IF din alte limbaje procedurale, permind efectuarea unor aciuni n mod selectiv, n funcie de anumite condiii. Instruciunea IF-THEN-ELSIF are urmtoarea form sintactic: IF condiie1 THEN secvena_de_comenzi_1 [ELSIF condiie2 THEN secvena_de_comenzi_2] [ELSE secvena_de_comenzi_n] END IF; O secven de comenzi din IF este executat numai n cazul n care condiia asociat este TRUE. Atunci cnd condiia este FALSE sau NULL, secvena nu este executat. Dac pe ramura THEN se dorete verificarea unei alternative, se folosete ramura ELSIF (atenie, nu ELSEIF) cu o nou condiie. Este permis un numr arbitrar de opiuni ELSIF, dar poate aprea cel mult o clauz ELSE. Aceasta se refer la ultimul ELSIF. Exemplu: S se specifice dac o galerie este mare, medie sau mica dup cum numrul operelor de art expuse n galeria respectiv este mai mare dect 200, cuprins ntre 100 i 200 sau mai mic dect 100.
SET SERVEROUTPUT ON DEFINE p_cod_gal = 753 DECLARE v_cod_galerie opera.cod_galerie%TYPE := &p_cod_gal; v_numar NUMBER(3) := 0; v_comentariu VARCHAR2(10); BEGIN SELECT COUNT(*) INTO v_numar FROM opera WHERE cod_galerie = v_cod_galerie; IF v_numar < 100 THEN v_comentariu := 'mica'; ELSIF v_numar BETWEEN 100 AND 200 THEN v_comentariu := 'medie'; ELSE v_comentariu := 'mare'; END IF; DBMS_OUTPUT.PUT_LINE('Galeria avand codul '||v_cod_galerie ||' este de tip '|| v_comentariu);

Programare n PL/SQL END; / SET SERVEROUTPUT OFF

257

Instruciunea CASE
Oracle9i furnizeaz o nou comand (CASE) care permite implementarea unor condiii multiple. Instruciunea are urmtoarea form sintactic: [<<eticheta>>] CASE test_var WHEN valoare_1 THEN secvena_de_comenzi_1; WHEN valoare_2 THEN secvena_de_comenzi_2; WHEN valoare_k THEN secvena_de_comenzi_k; [ELSE alt_secven;] END CASE [eticheta]; Se va executa secvena_de_comenzi_p, dac valoarea selectorului test_var este valoare_p. Dup ce este executat secvena de comenzi, controlul va trece la urmtoarea instruciune dup CASE. Selectorul test_var poate fi o variabil sau o expresie complex care poate conine chiar i apeluri de funcii. Clauza ELSE este opional. Dac aceast clauz este necesar n implementarea unei probleme, dar totui lipsete, iar test_var nu ia nici una dintre valorile ce apar n clauzele WHEN, atunci se declaneaz eroarea predefinit CASE_NOT_FOUND (ORA - 06592). Comanda CASE poate fi etichetat i, n acest caz, eticheta poate s apar la sfritul clauzei END CASE. De remarcat c eticheta dup END CASE este permis numai n cazul n care comanda CASE este etichetat. Selectorul test_var poate s lipseasc din structura comenzii CASE, care n acest caz va avea urmtoarea form sintactic: [<<eticheta>>] CASE WHEN condiie_1 THEN secvena_de_comenzi_1; WHEN condiie_2 THEN secvena_de_comenzi_2; WHEN condiie_k THEN secvena_de_comenzi_k; [ELSE alt_secven;] END CASE [eticheta]; Fiecare clauz WHEN conine o expresie boolean. Dac valoarea lui condiie_p este TRUE, atunci este executat secvena_de_comenzi_p. Exemplu: n funcie de o valoare introdus de utilizator, care reprezint abrevierea zilelor unei sptmni, s se afieze (n cele dou variante) un mesaj prin care este specificat ziua sptmnii corespunztoare abrevierii respective.

258
Varianta 1:

PROGRAMARE AVANSAT N ORACLE9i

SET SERVEROUTPUT ON DEFINE p_zi = x DECLARE v_zi CHAR(2) := UPPER('&p_zi'); BEGIN CASE v_zi WHEN 'L' THEN DBMS_OUTPUT.PUT_LINE('Luni'); WHEN 'M' THEN DBMS_OUTPUT.PUT_LINE('Marti'); WHEN 'MI' THEN DBMS_OUTPUT.PUT_LINE('Miercuri'); WHEN 'J' THEN DBMS_OUTPUT.PUT_LINE('Joi'); WHEN 'V' THEN DBMS_OUTPUT.PUT_LINE('Vineri'); WHEN 'S' THEN DBMS_OUTPUT.PUT_LINE('Sambata'); WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Duminica'); ELSE DBMS_OUTPUT.PUT_LINE('este o eroare!'); END CASE; END; / SET SERVEROUTPUT OFF

Varianta 2:
SET SERVEROUTPUT ON DEFINE p_zi = x DECLARE v_zi CHAR(2) := UPPER('&p_zi'); BEGIN CASE WHEN v_zi = 'L' THEN DBMS_OUTPUT.PUT_LINE('Luni'); WHEN v_zi = 'M' THEN DBMS_OUTPUT.PUT_LINE('Marti'); WHEN v_zi = 'MI' THEN DBMS_OUTPUT.PUT_LINE('Miercuri'); WHEN v_zi = 'J' THEN DBMS_OUTPUT.PUT_LINE('Joi'); WHEN v_zi = 'V' THEN DBMS_OUTPUT.PUT_LINE('Vineri'); WHEN v_zi = 'S' THEN DBMS_OUTPUT.PUT_LINE('Sambata'); WHEN v_zi = 'D' THEN DBMS_OUTPUT.PUT_LINE('Duminica'); ELSE DBMS_OUTPUT.PUT_LINE('Este o eroare!'); END CASE; END; / SET SERVEROUTPUT OFF

Oracle8i a implementat suportul pentru expresii CASE care sunt permise numai n comenzi SQL. n Oracle9i poate fi utilizat o construcie CASE ntr-o comand SQL a unui bloc PL/SQL. Expresia CASE are sintaxa similar comenzii CASE, dar clauzele WHEN nu se termin prin caracterul ;, clauza END nu include cuvntul cheie CASE i nu se fac atribuiri n clauza WHEN. Exemplu:
BEGIN

Programare n PL/SQL FOR j IN (SELECT CASE valoare WHEN 1000 THEN 1100 WHEN 10000 THEN 11000 WHEN 100000 THEN 110000 ELSE valoare END FROM opera) END LOOP; END;

259

Instruciuni iterative
Exist trei tipuri de comenzi iterative: ciclarea simpl LOOP, ciclarea WHILE i ciclarea FOR. Acestea permit repetarea (condiionat sau necondiionat) execuiei uneia sau mai multor instruciuni. Ciclurile pot fi imbricate pe mai multe niveluri. Ele pot fi etichetate, iar ieirea din ciclu se poate realiza cu ajutorul comenzii EXIT. Se utilizeaz:

comanda LOOP, dac instruciunile din cadrul ciclului trebuie s se execute cel puin o dat; comanda WHILE, n cazul n care condiia trebuie evaluat la nceputul fiecrei iteraii; comanda FOR, dac numrul de iteraii este cunoscut.

Instruciunea LOOP are urmtoarea form sintactic: LOOP secvena_de_comenzi; END LOOP; Ciclarea simpl cuprinde o mulime de comenzi incluse ntre cuvintele cheie LOOP i END LOOP. Aceste comenzi se execut cel puin o dat. Dac nu este utilizat comanda EXIT, ciclarea poate continua la infinit. Exemplu: Se presupune c a fost creat structura tabelului org_tab, constnd din dou coloane: cod_tab de tip INTEGER, ce conine un contor al nregistrrilor i text_tab de tip VARCHAR2, ce conine un text asociat fiecrei nregistrri. S se introduc 70 de nregistrri n tabelul org_tab.
DECLARE v_contor BINARY_INTEGER := 1; BEGIN LOOP INSERT INTO org_tab VALUES (v_contor, 'indicele ciclului'); v_contor := v_contor + 1; EXIT WHEN v_contor > 70;

260
END LOOP; END;

PROGRAMARE AVANSAT N ORACLE9i

Instruciunea repetitiv WHILE permite repetarea unei secvene de instruciuni, atta timp ct o anumit condiie specificat este adevrat. Comanda WHILE are urmtoarea sintax: WHILE condiie LOOP secvena_de_comenzi; END LOOP; Dac variabilele care apar n condiie nu se schimb n interiorul ciclului, atunci condiia rmne adevrat i ciclul nu se termin. Cnd condiia este evaluat ca fiind FALSE sau NULL, atunci secvena de comenzi nu este executat i controlul trece la prima instruciune dup END LOOP. Exemplu: Exemplul precedent poate fi implementat utiliznd comanda WHILE n urmtoarea manier:
DECLARE v_contor BINARY_INTEGER := 1; BEGIN WHILE v_contor <= 70 LOOP INSERT INTO org_tab VALUES (v_contor, 'indicele ciclului'); v_contor := v_contor + 1; END LOOP; END;

Instruciunea repetitiv FOR (ciclare cu pas) permite executarea unei secvene de instruciuni pentru valori ale variabilei contor cuprinse ntre dou limite, lim_inf i lim_sup. Dac este prezent opiunea REVERSE, iteraia se face (n sens invers) de la lim_sup la lim_inf. Comanda FOR are sintaxa: FOR contor_ciclu IN [REVERSE] lim_inf..lim_sup LOOP secvena_de_comenzi; END LOOP; Variabila contor_ciclu nu trebuie declarat. Ea este neidentificat n afara ciclului i implicit de tip BINARY_INTEGER. Pasul are implicit valoarea 1 i nu poate fi modificat. Limitele domeniului pot fi variabile sau expresii, care s poat fi convertite la ntreg. Exemplu: n structura tabelului opera se va introduce un nou cmp (stea). S se creeze un bloc PL/SQL care va reactualiza acest cmp, introducnd o stelu pentru fiecare 10000$ din valoarea unei opere de art al crei cod este specificat.
ALTER TABLE ADD stea opera VARCHAR2(20);

Programare n PL/SQL DEFINE p_cod_opera = 7777 DECLARE v_cod_opera opera.cod_opera%TYPE := &p_cod_opera; v_valoare opera.valoare%TYPE; v_stea opera.stea%TYPE := NULL; BEGIN SELECT NVL(ROUND(valoare/10000),0) INTO v_valoare FROM opera WHERE cod_opera = v_cod_opera; IF v_valoare > 0 THEN FOR i IN 1..v_valoare LOOP v_stea := v_stea || '*'; END LOOP; END IF; UPDATE opera SET stea = v_stea WHERE cod_opera = v_cod_opera; COMMIT; END;

261

Instruciuni de salt
Instruciunea EXIT permite ieirea dintr-un ciclu. Ea are o form necondiional (ieire fr condiii) i una condiional. Controlul trece fie la prima instruciune situat dup clauza END LOOP corespunztoare, fie dup instruciunea LOOP avnd eticheta nume_eticheta. EXIT [nume_eticheta] [WHEN condiie]; Numele etichetelor urmeaz aceleai reguli ca cele definite pentru identificatori. Eticheta se plaseaz naintea comenzii, fie pe aceeai linie, fie pe o linie separat. n PL/SQL etichetele se definesc prin intercalarea numelui etichetei ntre caracterele << i >> (<<eticheta>>). Exemplu:
DECLARE v_contor BINARY_INTEGER := 1; raspuns VARCHAR2(10); alt_raspuns VARCHAR2(10); BEGIN <<exterior>> LOOP v_contor := v_contor + 1; EXIT WHEN v_contor > 70; <<interior>> LOOP

262

PROGRAMARE AVANSAT N ORACLE9i

EXIT exterior WHEN raspuns = 'DA'; -- se parasesc ambele cicluri EXIT WHEN alt_raspuns = 'DA'; -- se paraseste ciclul interior END LOOP interior; END LOOP exterior; END;

Instruciunea GOTO determin un salt necondiionat la o instruciune executabil sau la nceputul unui bloc care are eticheta specificat n comand. Instruciunea are urmtoarea form sintactic: GOTO nume_eticheta; Nu este permis saltul: n interiorul unui bloc (subbloc); n interiorul unei comenzi IF, CASE sau LOOP; de la o clauz a comenzii CASE, la alt clauz a aceleai comenzi; de la tratarea unei excepii, n blocul curent; n exteriorul unui subprogram.

Instruciunea vid
Instruciunea vid (NULL) este folosit pentru o mai bun lizibilitate a programului. NULL este instruciunea care nu are nici un efect, marcnd faptul c nu trebuie ntreprins nici o aciune. Nu trebuie confundat instruciunea NULL cu valoarea null! Uneori instruciunea NULL este folosit ntr-o comand IF, indicnd faptul c pentru o anumit clauz ELSIF nu se execut nici o aciune.

4. Tipuri de date n PL/SQL


Fiecare variabil sau constant utilizat ntr-un bloc PL/SQL este de un anumit tip care determin formatul su de stocare, constrngerile pe care trebuie s le verifice i domeniul valorilor sale. Variabilele folosite n Oracle9i pot fi mprite n dou clase: variabile specifice PL/SQL, care se clasific n variabile de tip scalar, compuse, referin, LOB (large objects) i tipuri obiect; variabile nespecifice PL/SQL, care pot fi variabile de legtur (bind variables), variabile gazd (host variables) i variabile indicator.

Variabile specifice PL/SQL

Programare n PL/SQL

263

Tipurile de date scalare nu au componente interne (conin valori atomice). Aceste tipuri de date se mpart n cinci clase fundamentale. Tipurile de date ce stocheaz valori numerice, cuprind: tipul NUMBER cu subtipurile DEC, DECIMAL, DOUBLE PRECISION, INT, INTEGER, NUMERIC, FLOAT, REAL, SMALLINT; tipul de date BINARY_INTEGER cu subtipurile NATURAL, NATURALN, POSITIVE, POSITIVEN, SIGNTYPE; tipul PLS_INTEGER.. Tipurile de date ce stocheaz caractere, cuprind: tipul VARCHAR2 cu subtipurile STRING, VARCHAR; tipul de date CHAR cu subtipul CHARACTER; tipurile LONG, RAW, LONG RAW, ROWID. Tipurile de date ce stocheaz data calendaristic i ora, cuprind tipurile DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE, INTERVAL YEAR TO MONTH, INTERVAL DAY TO SECOND. Tipurile de date pentru globalizare, care includ tipurile NCHAR i NVARCHAR2, stocheaz date n format Unicode. Tipul de date BOOLEAN stocheaz valori logice (true, false sau null). Tipurile de date compuse au componente interne care pot fi prelucrate individual. Sistemul ofer programatorului dou tipuri de date compuse: nregistrare (RECORD) i colecie (INDEX-BY TABLE, NESTED TABLE, VARRAY). Tipurile de date referin (REF CURSOR, REF obiect) sunt tipuri de date ale cror valori, numite pointer-i, fac referin ctre obiecte din program. Pointer-ii conin locaia de memorie (adresa) a unui element i nu elementul n sine. Tipul REF CURSOR este folosit pentru a face referin la un cursor explicit. Tipul REF obiect face referin la adresa unui obiect. Tipurile LOB (large object) sunt acele tipuri de date ale cror valori, numite locatori (locators), specific locaia (localizarea) unor obiecte de dimensiuni mari, adic blocuri de date nestructurate, cum ar fi texte, imagini grafice, clipuri video i sunete. Ele cuprind tipurile: CLOB (Character Large Object), BLOB (Binary Large Object), NCLOB (National Language Character Large Object) i BFILE (Binary File). Tipurile LOB sunt prelucrate cu ajutorul pachetului DBMS_LOB. Tipurile obiect sunt tipuri compuse, definite de utilizator, care ncapsuleaz structuri de date (atribute) mpreun cu subprograme pentru prelucrarea datelor (metode). Oracle9i extinde modelul obiect de la versiunea Oracle8i, implementnd motenirea prin intermediul subtipurilor. Tipul obiect va fi analizat ntr-un capitol separat. Dintre tipurile scalare PL/SQL, urmtoarele sunt i tipuri SQL (adic pot fi folosite pentru coloanele tabelelor Oracle): NUMBER, VARCHAR2, CHAR, LONG, RAW, LONG RAW, ROWID, NCHAR, NVARCHAR2, DATE. n unele cazuri, tipurile de date PL/SQL difer de corespondentele lor din SQL prin dimensiunea maxim permis. Tipul NUMBER memoreaz numerele n virgul fix i virgul mobil. El are forma general NUMBER (m, n), unde m reprezint numrul total de cifre, iar n

264

PROGRAMARE AVANSAT N ORACLE9i

numrul de zecimale. Valoarea unei variabile de tip NUMBER este cuprins ntre 1.0E-129 i 9.99E125. Numrul de zecimale determin poziia n care apare rotunjirea. Valoarea sa este cuprins ntre -84 i 127, iar implicit este 0. Tipul NUMBER are urmtoarele subtipuri, care au aceleai intervale de valori: NUMERIC, REAL, DEC, DECIMAL i DOUBLE PRECISION (pentru memorarea datelor numerice n virgul fix), FLOAT (pentru memorarea datelor numerice n virgul mobil), SMALLINT, INTEGER i INT (pentru memorarea numerelor ntregi). Aceste subtipuri se pot utiliza pentru compatibilitate ANSI/ISO, IBM SQL/DS sau IBM DB2. Tipul BINARY_INTEGER memoreaz numere ntregi cu semn avnd valori cuprinse ntre -231 - 1 i 231 - 1. Acest tip de date este utilizat frecvent pentru indecii tabelelor, nu necesit conversii i admite mai multe subtipuri. De exemplu, pentru a restriciona domeniul variabilelor la valori ntregi nenegative se utilizeaz tipurile NATURAL (0 .. 231 1) i POSITIVE (1 .. 231 1). Tipul PLS_INTEGER este utilizat pentru stocarea numerelor ntregi cu semn i are acelai interval de definire ca i tipul BINARY_INTEGER. Operaiile cu acest tip sunt efectuate mai rapid (folosesc aritmetica mainii), dect cele cu tipurile NUMBER sau BINARY_INTEGER (folosesc librrii aritmetice). Prin urmare, pentru o mai bun performan, este preferabil s se utilizeze tipul PLS_INTEGER. Variabilele alfanumerice pot fi de tip CHAR, VARCHAR2, LONG, RAW i LONGRAW. Reprezentarea intern depinde de setul de caractere ales (ASCII sau EBCDIC). Tipurile CHAR, VARCHAR2 i RAW pot avea un parametru pentru a preciza lungimea maxim. Dac aceasta nu este precizat atunci, implicit, se consider 1. Lungimea este exprimat n octei (nu n caractere). Subtipurile acestor tipuri se pot utiliza pentru compatibilitate ANSI/ISO, IBM SQL/DS sau IBM DB2. n Oracle9i a fost extins sintaxa pentru CHAR i VARCHAR2, permind ca variabila ce precizeaz lungimea maxim s fie de tip CHAR sau BYTE. Variabilele de tip LONG pot memora texte, tabele de caractere sau documente, prin urmare iruri de caractere de lungime variabil de pn la 32760 octei. Este similar tipului VARCHAR2. Tipul RAW permite memorarea datelor binare (bii) sau a irurilor de octei. De exemplu, o variabil RAW poate memora o secven de caractere grafice sau o imagine digitizat. Tipul RAW este similar tipului alfanumeric, cu excepia faptului c PL/SQL nu interpreteaz datele de tip RAW. Oracle nu face conversia datelor de acest tip, atunci cnd se transmit de la un sistem la altul. Chiar dac lungimea maxim a unei variabile RAW poate fi 32767 octei, ntr-o coloan RAW a bazei de date nu se pot introduce dect 2000 octei. Pentru a insera valori mai mari se folosete o coloan de tip LONG RAW, care are lungimea maxim 231 octei. LONG RAW este similar tipului LONG, dar datele nu mai sunt interpretate de PL/SQL. Tipurile TIMESTAMP, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE, INTERVAL YEAR TO MONTH, INTERVAL DAY TO

Programare n PL/SQL

265

SECOND au fost introduse n Oracle9i i permit rafinri ale tipului DATE. De exemplu, TIMESTAMP poate lua n considerare i fraciuni de secund. PL/SQL suport dou seturi de caractere: una specific bazei de date care este utilizat pentru definirea identificatorilor i a codului surs (database character set - DCS) i o mulime de caractere naionale care este folosit pentru reprezentarea informaiei cu caracter naional (national character set - NCS). Tipurile de date NCHAR i NVARCHAR2 sunt utilizate pentru stocarea n baza de date a irurilor de caractere ce folosesc NCS. Ele ofer suport pentru globalizarea datelor, astfel nct utilizatorii din toat lumea pot interaciona cu Oracle n limba lor naional. Aceste tipuri de date suport numai date Unicode. Unicode este o mulime de caractere globale care permite stocarea de informaie n orice limb, folosind o mulime unic de caractere. Prin urmare, unicode furnizeaz o valoare cod unic pentru fiecare caracter, indiferent de platform, program sau limb.

Variabile nespecifice PL/SQL


Variabila de legtur (bind) se declar ntr-un mediu gazd i este folosit pentru transferul (la execuie) valorilor numerice sau de tip caracter n/din unul sau mai multe programe PL/SQL. Variabilele declarate n mediul gazd sau n cel apelant pot fi referite n instruciuni PL/SQL, dac acestea nu sunt n cadrul unei proceduri, funcii sau pachet. n SQL*Plus, variabilele de legtur se declar folosind comanda VARIABLE, iar pentru afiarea valorilor acestora se utilizeaz comanda PRINT. Ele sunt referite prin prefixarea cu simbolul :, pentru a putea fi deosebite de variabilele declarate n PL/SQL. Deoarece instruciunile SQL pot fi integrate n programe C, este necesar un mecanism pentru a transfera valori ntre mediul de programare C i instruciunile SQL care comunic cu server-ul bazei de date Oracle. n acest scop, n programul ncapsulat sunt definite variabilele gazd (host). Acestea sunt declarate ntre directivele BEGIN DECLARE SECTION i END DECLARE SECTION ale preprocesorului. O valoare null n baza de date nu are o valoare corespunztoare n mediul limbajului gazd (de exemplu, limbajul C). Pentru a rezolva problema comunicrii valorilor null ntre programul scris n limbaj gazd i sistemul Oracle, au fost definite variabilele indicator. Acestea sunt variabile speciale de tip ntreg, folosite pentru a indica dac o valoare null este recuperat (extras) din baza de date sau stocat n aceasta. Ele au urmtoarea form: :nume_extern [: indicator] De exemplu, dac atribuirea este fcut de limbajul gazd, valoarea 1 a indicatorului specific faptul c PL/SQL trebuie s nlocuiasc valoarea variabilei prin null, iar o valoare a indicatorului mai mare ca zero precizeaz c PL/SQL trebuie s considere chiar valoarea variabilei.

266 Declararea variabilelor

PROGRAMARE AVANSAT N ORACLE9i

Identificatorii PL/SQL trebuie declarai nainte de a fi referii n blocul PL/SQL. Dac n declaraia unei variabile apar referiri la alte variabile, acestea trebuie s fi fost declarate anterior. Orice variabil declarat ntr-un bloc este accesibil blocurilor coninute sintactic n acesta. Tipurile scalare sunt predefinite n pachetul STANDARD. Pentru a folosi un astfel de tip ntr-un program este suficient s fie declarat o variabil de tipul respectiv. Tipurile compuse sunt definite de utilizator. Prin urmare, n acest caz trebuie definit efectiv tipul i apoi declarat variabila de tipul respectiv. n declararea variabilelor pot fi utilizate atributele %TYPE i %ROWTYPE, care reprezint tipuri de date implicite. Aceste tipuri permit declararea unei variabile n concordan cu declaraii de variabile fcute anterior. Atributul %TYPE permite definirea unei variabile avnd tipul unei variabile declarate anterior sau tipul unei coloane dintr-un tabel. Atributul %ROWTYPE permite definirea unei variabile avnd tipul unei nregistrri dintr-un tabel. Avantajul utilizrii acestui atribut const n faptul c nu este necesar s se cunoasc numrul i tipurile coloanelor tabelului. Elementele individuale ale acestei structuri de tip nregistrare sunt referite n maniera clasic, prefixnd numele coloanei cu numele variabilei declarate. Calitatea atributelor %TYPE i %ROWTYPE const n faptul c simplific ntreinerea codului PL/SQL. De exemplu, poate fi modificat dimensiunea unei coloane, fr s fie necesar modificarea declaraiei variabilelor al cror tip s-a definit fcnd referin la tipul coloanei respective. Sintaxa declarrii unei variabile este urmtoarea: identificator [CONSTANT] {tip_de_date | identificator%TYPE | identificator%ROWTYPE} [NOT NULL] [ {:= | DEFAULT} expresie_PL/SQL]; Se pot defini constante (valoarea stocat nu poate fi modificat) prin specificarea la declarare a cuvntului cheie CONSTANT. Exemplu:
v_valoare v_data_achizitie v_material c_valoare v_stare v_clasificare v_cod_opera v_opera int_an_luna NUMBER(15) NOT NULL := 0; DATE DEFAULT SYSDATE; VARCHAR2(15) := 'Matase'; CONSTANT NUMBER := 100000; VARCHAR2(20) DEFAULT 'Buna'; BOOLEAN DEFAULT FALSE; opera.cod_opera%TYPE; opera%ROWTYPE; INTERVAL YEAR TO MONTH := INTERVAL '3-2' YEAR TO MONTH;

Observaii:

Programare n PL/SQL

267

Pentru a denumi o variabil este utilizat frecvent (pentru uurina referirii) prefixarea cu litera v (v_identificator), iar pentru o constant este folosit prefixarea cu litera c (c_identificator). Variabilele pot fi iniializate, iar dac o variabil nu este iniializat, valoarea implicit a acesteia este null. Dac o variabil este declarat NOT NULL, atunci ea va fi obligatoriu iniializat. Pentru a iniializa o variabil sau o constant poate fi utilizat o expresie PL/SQL compatibil ca tip cu variabila sau constanta respectiv. Constantele trebuie iniializate cnd sunt declarate, altfel apare o eroare la compilare. n seciunea declarativ, pe fiecare linie, exist o singur declaraie de variabil. Dou obiecte (variabile) pot avea acelai nume cu condiia s fie definite n blocuri diferite. Dac ele coexist, poate fi folosit doar obiectul declarat n blocul curent. Atributul %ROWTYPE nu poate include clauze de iniializare.

Definirea subtipurilor
Subtipurile deriv dintr-un tip de baz, la care se adaug anumite restricii. De exemplu, NATURAL este un subtip predefinit PL/SQL, derivat din tipul de baz BINARY_INTEGER, cu restricia c permite prelucrarea valorilor ntregi nenegative. Prin urmare, un subtip nu reprezint un nou tip de date, ci un tip existent asupra cruia se aplic anumite constrngeri. Subtipurile presupun acelai set de operaii ca i tipul de baz, dar aplicate unui subset de valori al acestui tip. Sistemul Oracle permite ca utilizatorul s-i defineasc propriile sale tipuri i subtipuri de date n partea declarativ a unui bloc PL/SQL, subprogram sau pachet utiliznd sintaxa: SUBTYPE nume_subtip IS tip_de_baza [NOT NULL]; n dicionarul datelor exist vizualizri care furnizeaz informaii despre tipurile de date create de utilizator (USER_TYPES, USER_TYPE_ATTRS).

Conversii ntre tipuri de date


Exist dou tipuri de conversii, implicite i explicite. PL/SQL face automat conversii implicite ntre caractere i numere sau ntre caractere i date calendaristice. Chiar dac sistemul realizeaz automat aceste conversii, n practic se utilizeaz frecvent funcii de conversie explicit. Funciile de conversie explicit din SQL sunt utilizabile i n PL/SQL. Acestea sunt: TO_NUMBER, TO_CHAR, TO_DATE, TO_MULTI_BYTE, TO_SINGLE_BYTE, TO_CLOB, TO_LOB, CHARTOROWID, ROWIDTOCHAR, RAWTOHEX, HEXTORAW. n Oracle9i se pot folosi urmtoarele funcii de conversie: ASCIISTR, BIN_TO_NUM, NUMTODSINTERVAL, TO_TIMESTAMP, TO_YMINTERVAL,

268

PROGRAMARE AVANSAT N ORACLE9i

TO_NCHAR, TO_NCLOB, TO_TIMESTAMP_TZ, NUMTOYMINTERVAL, TO_DSINTERVAL, REFTOHEX, RAWTOHEX, RAWTONHEX, FROM_TZ, ROWIDTONCHAR, COMPOSE, DECOMPOSE. Denumirile acestor funcii reflect posibilitile pe care le ofer. De exemplu, TO_YMINTERVAL convertete argumentele sale la tipul INTERVAL YEAR TO MONTH conform unui format specificat. Funcia COMPOSE convertete un ir de caractere la un ir unicode (asociaz o valoare cod unic pentru fiecare simbol din ir).

nregistrri
Tipul RECORD ofer un mecanism pentru prelucrarea nregistrrilor. nregistrrile au mai multe cmpuri ce pot fi de tipuri diferite, dar care sunt legate din punct de vedere logic. Declararea tipului RECORD se face conform urmtoarei sintaxe: TYPE nume_tip IS RECORD (nume_cmp1 {tip_cmp | variabil%TYPE | nume_tabel.coloan%TYPE | nume_tabel%ROWTYPE} [ [NOT NULL] {:= | DEFAULT} expresie1], (nume_cmp2 {tip_cmp | variabil%TYPE | nume_tabel.coloan%TYPE | nume_tabel%ROWTYPE} [ [NOT NULL] {:= | DEFAULT} expresie2],); Identificatorul nume_tip reprezint numele tipului RECORD care se va specifica n declararea nregistrrilor, nume_cmp este numele unui cmp al nregistrrii, iar tip_cmp este tipul de date al cmpului. Observaii:

Dac un cmp nu este iniializat, atunci se consider implicit c are valoarea null. Dac s-a specificat constrngerea NOT NULL, atunci obligatoriu cmpul trebuie iniializat, iar iniializarea se face cu o valoare diferit de null. Pentru referirea cmpurilor individuale din nregistrare se prefixeaz numele cmpului cu numele nregistrrii. Pot fi asignate valori unei nregistrri utiliznd comenzile SELECT, FETCH sau instruciunea clasic de atribuire. De asemenea, o nregistrare poate fi asignat altei nregistrri de acelai tip. Componentele unei nregistrri pot fi de tip scalar, RECORD, TABLE, obiect, colecie (dar nu de tipul REF CURSOR). PL/SQL permite declararea i referirea nregistrrilor imbricate. Numrul de cmpuri ale unei nregistrri nu este limitat. nregistrrile nu pot fi comparate (egalitate, inegalitate sau null). nregistrrile pot fi parametri n subprograme i pot s apar n clauza RETURN a unei funcii.

Diferena dintre atributul %ROWTYPE i tipul de date compus RECORD este c tipul RECORD permite specificarea tipului de date pentru cmpuri i declararea acestora. Numele cmpului poate coincide cu numele unei coloane.

Programare n PL/SQL

269

Oracle9i introduce cteva faciliti legate de acest tip de date. Se poate insera (INSERT) o linie ntr-un tabel utiliznd o nregistrare. Nu mai este necesar listarea cmpurilor individuale, ci este suficient utilizarea numelui nregistrrii. Se poate reactualiza (UPDATE) o linie a unui tabel utiliznd o nregistrare. Sintaxa SET ROW permite s se reactualizeze ntreaga linie folosind coninutul unei nregistrri. ntr-o nregistrare se poate regsi i returna sau terge informaia din clauza RETURNING a comenzilor UPDATE sau DELETE. Dac n comenzile UPDATE sau DELETE se modific mai multe linii, atunci pot fi utilizate n sintaxa BULK COLLECT INTO, colecii de nregistrri. Exemplu: Exemplul urmtor arat modul n care poate s fie utilizat o nregistrare n clauza RETURNING asociat comenzii DELETE.
DECLARE TYPE val_opera IS RECORD ( cheie NUMBER, val NUMBER); v_info_valoare val_opera; BEGIN DELETE FROM opera WHERE cod_opera = 753 RETURNING cod_opera, valoare INTO v_info_valoare; END;

n PL/SQL este folosit frecvent tipul tablou de nregistrri. Referirea la un element al tabloului se face prin forma clasic: tabel(index).cmp. Exemplu: S se defineasc un tablou de nregistrri avnd tipul celor din tabelul organizator. S se iniializeze un element al tabloului i s se introduc n tabelul organizator. S se tearg elementele tabloului.
DECLARE TYPE org_table_type IS TABLE OF organizator%ROWTYPE INDEX BY BINARY INTEGER; org_table org_table_type; i NUMBER; BEGIN IF org_table.COUNT <>0 THEN i := org_table.LAST+1; ELSE i:=1; END IF; org_table(i).cod_org := 752; org_table(i).nume := 'Grigore Ion'; org_table(i).adresa := 'Calea Plevnei 18 Sibiu';

270

PROGRAMARE AVANSAT N ORACLE9i

org_table(i).tip := 'persoana fizica'; INSERT INTO organizator VALUES (org_table(i).cod_org, org_table(i).nume, org_table(i).adresa, org_table(i).tip); -- sau folosind noua facilitate Oracle9i -- INSERT INTO organizator -- VALUES (org_table(i)); org_table.DELETE; DBMS_OUTPUT.PUT_LINE('Dupa aplicarea metodei DELETE sunt ' ||TO_CHAR(org_table.COUNT)||' elemente'); END;

Colecii
Uneori este preferabil s fie prelucrate simultan mai multe variabile de acelai tip. Tipurile de date care permit acest lucru sunt coleciile. Fiecare element are un indice unic, care determin poziia sa n colecie. Oracle7 a furnizat tipul index-by table, iniial numit PL/SQL table datorit asemnrii sale cu structura tabelelor relaionale. Oracle8 a introdus dou tipuri colecie, nested table i varray. Oracle9i permite crearea de colecii pe mai multe niveluri, adic colecii de colecii. Prin urmare, n PL/SQL exist trei tipuri de colecii: tablouri indexate (index-by tables); tablouri imbricate (nested tables); vectori (varrays sau varying arrays). Tipul index-by table poate fi utilizat numai n declaraii PL/SQL. Tipurile varray i nested table pot fi utilizate att n declaraii PL/SQL, ct i n declaraii la nivelul schemei (de exemplu, pentru definirea tipului unei coloane a unui tabel relaional). Exemplu: n exemplul care urmeaz sunt ilustrate cele trei tipuri de colecii.
DECLARE TYPE tab_index IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; TYPE tab_imbri IS TABLE OF NUMBER; TYPE vector IS VARRAY(15) OF NUMBER; v_tab_index tab_index; v_tab_imbri tab_imbri; v_vector vector; BEGIN v_tab_index(1) := 72; v_tab_index(2) := 23; v_tab_imbri := tab_imbri(5, 3, 2, 8, 7); v_vector := vector(1, 2); END;

Observaii:

Programare n PL/SQL

271

Deoarece coleciile nu pot fi comparate (egalitate sau inegalitate), ele nu pot s apar n clauzele DISTINCT, GROUP BY, ORDER BY. Tipul colecie poate fi definit ntr-un pachet. Tipul colecie poate s apar n clauza RETURN a unei funcii. Coleciile pot fi parametri formali ntr-un subprogram. Accesul la elementele individuale ale unei colecii se face prin utilizarea unui indice.

Tablouri indexate
Tipul de date index-by table ofer un mecanism pentru prelucrarea tablourilor. Tabloul indexat PL/SQL are dou componente: o coloan ce cuprinde cheia primar pentru acces la liniile tabloului i o coloan care include valoarea efectiv a elementelor tabloului. Oracle7 asigur definirea tablourilor de nregistrri care pot fi declarate i utilizate numai n programe PL/SQL, Oracle8 realizeaz definirea tablourilor de tipuri obiect, iar Oracle9i permite definirea tablourilor de colecii. n Oracle9i tipul index-by table este redenumit associative array pentru compatibilitate (de limbaj) cu termenul folosit n alte limbaje de programare (C++, JavaScript, Perl) pentru a defini aceast structur de date. Tablourile indexate PL/SQL trebuie definite n doi pai: se definete tipul TABLE; se declar tabloul indexat PL/SQL de acest tip. Declararea tipului TABLE se face respectnd urmtoarea sintax: TYPE nume_tip IS TABLE OF {tip_coloan | variabil%TYPE | nume_tabel.coloan%TYPE [NOT NULL] | nume_tabel%ROWTYPE} INDEX BY tip_indexare; Identificatorul nume_tip este numele noului tip definit care va fi specificat n declararea tabloului PL/SQL, iar tip_coloan este un tip scalar simplu (de exemplu, VARCHAR2, CHAR, DATE sau NUMBER). Pn la versiunea Oracle9i unicul tip de indexare acceptat era INDEX BY BINARY_INTEGER. Oracle9i permite urmtoarele opiuni pentru tip_indexare: PLS_INTEGER, NATURAL, POSITIVE, VARCHAR2(n) sau chiar indexarea dup un tip declarat cu %TYPE. Nu sunt permise indexrile INDEX BY NUMBER, INDEX BY INTEGER, INDEX BY DATE, INDEX BY VARCHAR2, INDEX BY CHAR(n) sau indexarea dup un tip declarat cu %TYPE n care intervine unul dintre tipurile enumerate anterior. Observaii: Elementele unui tablou indexat nu sunt ntr-o ordine particular i pot fi inserate cu chei arbitrare. Deoarece nu exist constrngeri de dimensiune, dimensiunea tabloului se modific dinamic. Tabloul indexat PL/SQL nu trebuie iniializat.

272

PROGRAMARE AVANSAT N ORACLE9i

Un tablou indexat neiniializat este vid. Un element al tabloului este nedefinit atta timp ct nu are atribuit o valoare efectiv. Iniial, un tablou indexat este nedens. Dup declararea unui tablou se poate face referire la liniile lui prin precizarea valorii cheii primare. Dac se face referire la o linie care nu exist, atunci se produce excepia NO_DATA_FOUND. Dac se dorete contorizarea numrului de linii, trebuie declarat o variabil n acest scop sau poate fi utilizat o metod asociat tabloului. Deoarece numrul de linii nu este limitat, operaia de adugare de linii este restricionat doar de dimensiunea memoriei alocate. Tablourile pot s apar ca argumente ntr-o procedur.

Pentru inserarea unor valori din tablourile PL/SQL ntr-o coloan a unui tabel de date se utilizeaz instruciunea INSERT n cadrul unei secvene repetitive LOOP. Asemntor, pentru regsirea unor valori dintr-o coloan a unei baze de date ntr-un tablou PL/SQL se utilizeaz instruciunea FETCH (cursoare) sau instruciunea de atribuire n cadrul unei secvene repetitive LOOP. Pentru a terge liniile unui tablou fie se asigneaz elementelor tabloului valoarea null, fie se declar un alt tablou PL/SQL (de acelai tip) care nu este iniializat i acest tablou vid se asigneaz tabloului PL/SQL care trebuie ters. n PL/SQL 2.3 tergerea liniilor unui tabel se poate face utiliznd metoda DELETE. Exemplu: S se defineasc un tablou indexat PL/SQL avnd elemente de tipul NUMBER. S se introduc 20 de elemente n acest tablou. S se tearg tabloul.
DECLARE TYPE tablou_numar IS TABLE OF NUMBER INDEX BY PLS_INTEGER; v_tablou tablou_numar; BEGIN FOR i IN 1..20 LOOP v_tablou(i) := i*i; DBMS_OUTPUT.PUT_LINE(v_tablou(i)); END LOOP; --v_tablou := NULL; --aceasta atribuire da eroarea PLS-00382 FOR i IN v_tablou.FIRST..v_tablou.LAST LOOP v_tablou(i) := NULL; END LOOP; DBMS_OUTPUT.PUT_LINE('tabloul are ' || v_tablou.COUNT || ' elemente'); END;

Vectori

Programare n PL/SQL

273

Vectorii (varray) sunt structuri asemntoare vectorilor din limbajele C sau Java. Spre deosebire de tablourile indexate, vectorii au o dimensiune maxim (constant) stabilit la declarare. n special, se utilizeaz pentru modelarea relaiilor one-to-many, atunci cnd numrul maxim de elemente din partea many este cunoscut i ordinea elementelor este important. Vectorii reprezint structuri dense. Fiecare element are un index care d poziia sa n vector i care este folosit pentru accesarea elementelor particulare. Limita inferioar a indicelui este 1. Vectorul poate conine un numr variabil de elemente, de la 0 (vid) la numrul maxim specificat obligatoriu n definiia sa. Tipul de date vector este declarat utiliznd sintaxa: TYPE nume_tip IS {VARRAY | VARYING ARRAY } (lungime_maxim) OF tip_elemente [NOT NULL]; Identificatorul nume_tip este numele tipului de date vector, iar lungime_maxim reprezint numrul maxim de elemente din vector. Tip_elemente este un tip scalar PL/SQL, tip nregistrare sau tip obiect. De asemenea, acest tip poate fi definit utiliznd atributele %TYPE sau %ROWTYPE. n Oracle9i sunt permise (pentru tip_elemente) tipurile TABLE sau alt tip VARRAY. Exist restricii referitoare la tipul elementelor, n sensul c acesta nu poate s fie BOOLEAN, NCHAR, NCLOB, NVARCHAR2, REF CURSOR, PLS_INTEGER, LONG, LONG RAW, NATURAL, NATURALN, POSITIVE, POSITIVEN, BINARY_INTEGER, SIGNTYPE, STRING, tip obiect cu atribute TABLE sau VARRAY, BLOB, CLOB, tip obiect cu atribute BLOB sau CLOB. Exemplu:
DECLARE TYPE secventa IS VARRAY(5) OF VARCHAR2(10); v_sec secventa := secventa ('alb','negru','rosu','verde'); BEGIN v_sec (3) := 'rosu'; v_sec.EXTEND; v_sec(5) := 'albastru'; -- extinderea la 6 elemente va genera eroarea ORA-06532 v_sec.EXTEND; END;

Tablouri imbricate
Tablourile imbricate (nested table) sunt tablouri indexate a cror dimensiune nu este stabilit. Un tablou imbricat este o mulime neordonat de elemente de acelai tip. Valorile de acest tip pot fi stocate n baza de date, pot fi prelucrate direct n instruciuni SQL i au excepii predefinite proprii. Numrul maxim de linii ale unui tablou imbricat este dat de capacitatea maxim 2 GB. Sistemul Oracle nu stocheaz liniile unui tablou imbricat ntr-o ordine particular. Dar, cnd se regsete tabloul n variabile PL/SQL, liniile vor avea

274

PROGRAMARE AVANSAT N ORACLE9i

indici consecutivi ncepnd cu valoarea 1. Iniial, aceste tablouri sunt structuri dense, dar se poate ca n urma prelucrrii s nu mai aib indici consecutivi. Comanda de declarare a tipului de date tablou imbricat are sintaxa: TYPE nume_tip IS TABLE OF tip_ elemente [NOT NULL]; Identificatorul nume_tip reprezint numele noului tip de date tablou imbricat, iar tip_elemente este tipul fiecrui element din tabloul imbricat, care poate fi un tip definit de utilizator sau o expresie cu %TYPE, respectiv %ROWTYPE. n Oracle9i sunt permise (pentru tip_elemente) tipurile TABLE sau alt tip VARRAY. Exist restricii referitoare la tipul elementelor, n sensul c acesta nu poate s fie BOOLEAN, STRING, NCHAR, NCLOB, NVARCHAR2, REF CURSOR, BINARY_INTEGER, PLS_INTEGER, LONG, LONG RAW, NATURAL, NATURALN, POSITIVE, POSITIVEN, SIGNTYPE, tip obiect cu atributele TABLE sau VARRAY. Tabloul imbricat are o singur coloan, iar dac aceasta este de tip obiect, tabloul poate fi vizualizat ca un tabel multicoloan, avnd cte o coloan pentru fiecare atribut al tipului obiect. Exemplu:
DECLARE TYPE numartab IS TABLE OF NUMBER; -- se creeaza un tablou cu un singur element v_tab_1 numartab := numartab(-7); -- se creeaza un tablou cu 4 elemente v_tab_2 numartab := numartab(7,9,4,5); -- se creeaza un tablou fara nici un element v_tab_3 numartab := numartab(); BEGIN v_tab_1(1) := 57; FOR j IN 1..4 LOOP DBMS_OUTPUT.PUT_LINE (v_tab_2(j) || ' '); END LOOP; END;

Se observ c singura diferen sintactic ntre tablourile indexate i cele imbricate este absena clauzei INDEX BY BINARY_INTEGER. Mai exact, dac aceast clauz lipsete, tipul este tablou imbricat. Observaii: Spre deosebire de tablourile indexate, vectorii i tablourile imbricate pot s apar n definirea tabelelor bazei de date. Tablourile indexate pot avea indice negativ, domeniul permis pentru index fiind 2147483647..2147483647, iar pentru tabele imbricate domeniul indexului este 1..2147483647. Tablourile imbricate, spre deosebire de tablourile indexate, pot fi prelucrate prin comenzi SQL. Tablourile imbricate trebuie iniializate i/sau extinse pentru a li se

Programare n PL/SQL

275

aduga elemente. Cnd este creat un tablou indexat care nu are nc elemente, el este vid. Dac un tablou imbricat (sau un vector) este declarat, dar nu are nc nici un element (nu este iniializat), el este automat iniializat (atomic) null. Adic, colecia este null, nu elementele sale. Prin urmare, pentru tablouri imbricate poate fi utilizat operatorul IS NULL. Dac se ncearc s se adauge un element la un tablou imbricat null, se va genera eroarea ORA - 06531: reference to uninitialized collection care corespunde excepiei predefinite COLLECTION_IS_NULL. Prin urmare, cum poate fi iniializat un tablou imbricat? Ca i obiectele, vectorii i tablourile imbricate sunt iniializate cu ajutorul constructorului. Acesta are acelai nume ca i tipul coleciei referite. PL/SQL apeleaz un constructor numai n mod explicit. Tabelele indexate nu au constructori. Constructorul primete ca argumente o list de valori de tip tip_elemente. Elementele sunt numerotate n ordine, de la 1 la numrul de valori date ca parametrii constructorului. Dimensiunea iniial a coleciei este egal cu numrul de argumente date n constructor, cnd aceasta este iniializat. Pentru vectori nu poate fi depit dimensiunea maxim precizat la declarare. Atunci cnd constructorul este fr argumente, va crea un obiect fr nici un element, dar care nu are valoarea null. Exemplul urmtor este concludent n acest sens. Exemplu:
DECLARE TYPE alfa IS TABLE OF VARCHAR2(50); -- creeaza un tablou null tab1 alfa ; -- creeaza un tablou cu un element care este null tab2 alfa := alfa() ; BEGIN IF tab1 IS NULL THEN DBMS_OUTPUT.PUT_LINE('tab1 este NULL'); ELSE DBMS_OUTPUT.PUT_LINE('tab1 este NOT NULL'); END IF; IF tab2 IS NULL THEN DBMS_OUTPUT.PUT_LINE('tab2 este NULL'); ELSE DBMS_OUTPUT.PUT_LINE('tab2 este NOT NULL'); END IF; END;

n urma execuiei acestui bloc se obine urmtorul rezultat:


tab1 este NULL tab2 este NOT NULL

Excepiile semnificative care apar n cazul utilizrii incorecte a coleciilor

276

PROGRAMARE AVANSAT N ORACLE9i

sunt prezentate n exemplul care urmeaz. Exemplu:


DECLARE TYPE numar IS TABLE OF INTEGER; alfa numar; BEGIN alfa(1) := 77; -- declanseaza exceptia COLLECTION_IS_NULL alfa := numar(15, 26, 37); alfa(1) := ASCII('X'); alfa(2) := 10*alfa(1); alfa('P') := 77; /* declanseaza exceptia VALUE_ERROR deoarece indicele nu este convertibil la intreg */ alfa(4) := 47; /* declanseaza exceptia SUBSCRIPT_BEYOND_COUNT deoarece indicele se refera la un element neinitializat */ alfa(null) := 7; -- declanseaza exceptia VALUE_ERROR alfa(0) := 7; -- exceptia SUBSCRIPT_OUTSIDE_LIMIT alfa.DELETE(1); IF alfa(1) = 1 THEN -- exceptia NO_DATA_FOUND END;

Tablourile imbricate i vectorii pot fi utilizai drept cmpuri n tabelele bazei. Aceasta presupune c fiecare nregistrare din tabelul respectiv conine un obiect de tip colecie. nainte de utilizare, tipul trebuie stocat n dicionarul datelor, deci trebuie declarat prin comanda: CREATE TYPE nume_tip AS {TABLE | VARRAY} OF tip_elemente; Dup crearea tabelului (prin comanda CREATE TABLE), pentru fiecare cmp de tip tablou imbricat din tabel este necesar clauza de stocare: NESTED TABLE nume_cmp STORE AS nume_tabel;

Colecii pe mai multe niveluri


n Oracle9i se pot construi colecii pe mai multe niveluri (multilevel collections), prin urmare colecii ale cror elemente sunt, n mod direct sau indirect, colecii. n felul acesta pot fi definite structuri complexe: vectori de vectori, vectori de tablouri imbricate, tablou imbricat de vectori, tablou imbricat de tablouri imbricate, tablou imbricat sau vector de un tip definit de utilizator care are un atribut de tip tablou imbricat sau vector. Aceste structuri complexe pot fi utilizate ca tipuri de date pentru definirea coloanelor unui tabel relaional, ale atributelor unui obiect ntr-un tabel obiect sau ale variabilelor PL/SQL. Numrul nivelurilor de imbricare este limitat doar de

Programare n PL/SQL

277

capacitatea de stocare a sistemului. Pentru a accesa un element al coleciei incluse sunt utilizate dou seturi de paranteze. Obiectele de tipul colecie pe mai multe niveluri nu pot fi comparate. Exemplu: n exemplele care urmeaz sunt definite trei structuri complexe i sunt prezentate cteva modaliti de utilizare ale acestora. Exemplele se refer la vectori pe mai multe niveluri, tablouri imbricate pe mai multe niveluri i tablouri indexate pe mai multe niveluri.
DECLARE TYPE alfa IS VARRAY(10) OF INTEGER; TYPE beta IS VARRAY(10) OF alfa; valf alfa := alfa(12,31,5); --initializare vbet beta := beta(valf,alfa(55,6,77),alfa(2,4),valf); i integer; var1 alfa; BEGIN i := vbet(2)(3); -- i va lua valoarea 77 vbet.EXTEND; -- se adauga un element de tip vector la vbet vbet(5) := alfa(56,33); vbet(4) := alfa(44,66,77,4321); vbet(4)(4) := 7; -- 4321 este inlocuit cu 7 vbet(4).EXTEND; -- se adauga un element la al 4-lea element vbet(4)(5) := 777; -- acest nou element adaugat va fi 777 END; / DECLARE TYPE gama IS TABLE OF VARCHAR2(20); TYPE delta IS TABLE OF gama; TYPE teta IS VARRAY(10) OF INTEGER; TYPE epsi IS TABLE OF teta; var1 gama := gama('alb','negru'); var2 delta := delta(var1); var3 epsi := epsi(teta(31,15),teta(1,3,5)); BEGIN var2.EXTEND; var2(2) := var2(1); var2.DELETE(1); -- sterge primul element din var2 /* sterge primul sir de caractere din al doilea tabel al tabelului imbricat */ var2(2).DELETE(1); END; / DECLARE TYPE alfa IS TABLE OF INTEGER INDEX BY BINARY_INTEGER; TYPE beta IS TABLE OF alfa INDEX BY BINARY_INTEGER;

278

PROGRAMARE AVANSAT N ORACLE9i

TYPE gama IS VARRAY(10) OF VARCHAR2(30); TYPE delt IS TABLE OF gama INDEX BY BINARY_INTEGER; var1 gama := gama('alb','negru'); var2 beta; var3 delt; var4 alfa; var5 alfa; -- tabel null BEGIN var4(1) := 324; var4(2) := 222; var4(42) := 333; var2(27) := var4; var3(39) := gama(77,76,89,908); -- var2(40)(3) := 55; eroare nu exista elementul 40 in var2 var2(40) := var5; -- asignez un tabel null var2(40)(3) := 55; -- corect END; /

Prelucrarea coleciilor
O colecie poate fi exploatat fie n ntregime (atomic) utiliznd comenzi LMD, fie pot fi prelucrate elemente individuale dintr-o colecie (piecewise updates) utiliznd operatori SQL sau anumite faciliti oferite de PL/SQL. Comanda INSERT permite inserarea unei colecii ntr-o linie a unui tabel. Colecia trebuie s fie creat i iniializat anterior. Comanda UPDATE este folosit pentru modificarea unei colecii stocate, iar DELETE poate terge o linie ce conine o colecie. Coleciile din baza de date pot fi regsite n variabile PL/SQL, utiliznd comanda SELECT. Exemplu:
CREATE OR REPLACE TYPE operalist AS VARRAY(10) OF NUMBER(4); CREATE TABLE gal_ope ( cod_galerie NUMBER(10), nume_galerie VARCHAR2(20), info operalist); DECLARE v_opera operalist := operalist (777, 888, 999); v_info_op operalist := operalist (7007); v_info gal_ope.info%TYPE; v_cod gal_ope.cod_galerie%TYPE; BEGIN INSERT INTO gal_ope VALUES (4567, 'Impresionisti', operalist(4567,4987)); INSERT INTO gal_ope VALUES (2345, 'Cubism', v_opera); INSERT INTO gal_ope

Programare n PL/SQL VALUES SELECT INTO FROM WHERE END; (123, 'Alfa', v_info_op); info v_info gal_ope cod_galerie = v_cod;

279

Un vector stocat ntr-un tabel este prelucrat ca un ntreg (nu pot fi modificate elemente individuale). Prin urmare, elementele individuale ale unui vector nu pot fi referite n comenzile INSERT, UPDATE sau DELETE. Pentru referirea acestora trebuie utilizate comenzi procedurale PL/SQL. Pentru a modifica un vector, el trebuie selectat ntr-o variabil PL/SQL a crei valoare poate fi modificat i apoi reinserat n tabel. Tablourile imbricate depuse n baza de date sunt mai flexibile, deoarece pot fi prelucrate fie n ntregime, fie ca elemente individuale. n fiecare caz pot fi utilizate numai comenzi SQL. Se pot face reactualizri sau inserri asupra tablourilor imbricate care dau o valoare nou pentru ntreaga colecie sau se pot face inserri, tergeri, reactualizri de elemente particulare din colecie. O colecie poate fi asignat altei colecii prin comenzile INSERT, UPDATE, FETCH, SELECT, instruciunea de atribuire sau prin apelul unui subprogram, dar coleciile trebuie s fie de acelai tip. Dac unei colecii i se asigneaz o colecie atomic null, aceasta devine atomic null i trebuie reiniializat. n Oracle8i a fost introdus operatorul TABLE, ce permite prelucrarea elementelor unui tablou imbricat care este stocat ntr-un tabel. Operatorul permite interogarea unei colecii n clauza FROM (la fel ca un tabel). Operandul lui TABLE este fie numele unei colecii i atunci rezultatul operandului este tot o colecie, fie este o subinterogare referitoare la o colecie, iar n acest caz, operatorul TABLE returneaz o singur valoare (coloan) care este un tablou imbricat sau un vector. Prin urmare, lista din clauza SELECT a subcererii trebuie s aib un singur articol. Subcererea nu poate returna colecii pentru mai multe linii. Exemplu: Se presupune c tabelul opera are o coloan info de tip tablou imbricat. Acest tablou are dou componente n care pentru fiecare oper de art sunt depuse numele articolului referitor la opera respectiv i revista n care a aprut. S se insereze o linie n tabelul imbricat.
INSERT INTO TABLE (SELECT info FROM opera WHERE titlu = 'Primavara') VALUES ('Pictura moderna', 'Orizonturi');

Listarea codului fiecrei opere de art i a coleciei articolelor referitoare la aceste opere de art se face prin comanda:
SELECT FROM a.cod_opera, b.* opera a, TABLE (a.info) b;

280

PROGRAMARE AVANSAT N ORACLE9i

Pentru tablouri imbricate pe mai multe niveluri, operaiile LMD pot fi fcute atomic sau pe elemente individuale, iar pentru vectori pe mai multe niveluri, operaiile pot fi fcute numai atomic. Pentru prelucrarea unei colecii locale se folosesc operatorii TABLE i CAST. CAST are forma sintactic: CAST (nume_colecie AS tip_colecie) Operanzii lui CAST sunt o colecie declarat local (de exemplu, ntr-un bloc PL/SQL anonim) i un tip colecie SQL. CAST convertete colecia local la tipul specificat. n felul acesta, o colecie poate fi prelucrat ca i cum ar fi un tabel SQL al bazei de date.

Metodele unei colecii


PL/SQL ofer subprograme numite metode (methods), care opereaz asupra unei colecii. Acestea pot fi apelate numai din comenzi procedurale, i nu din SQL. Metodele sunt apelate prin expresia: nume_colecie.nume_metod [ (parametri) ] Metodele care se pot aplica coleciilor PL/SQL sunt urmtoarele: COUNT returneaz numrul curent de elemente ale unei colecii PL/SQL; DELETE(n) terge elementul n dintr-o colecie PL/SQL; DELETE(m, n) terge toate elementele avnd indecii ntre m i n; DELETE terge toate elementele unei colecii PL/SQL (nu este valid pentru tipul varrays); EXISTS(n) returneaz TRUE dac exist al n-lea element al unei colecii PL/SQL (altfel, returneaz FALSE, chiar dac elementul este null); FIRST, LAST returneaz indicele primului, respectiv ultimului element din colecie; NEXT(n), PRIOR(n) returneaz indicele elementului urmtor, respectiv precedent celui de rang n din colecie, iar dac nu exist un astfel de element returneaz valoarea null; EXTEND adaug elemente la sfritul unei colecii: EXTEND adaug un element null la sfritul coleciei, EXTEND(n) adaug n elemente null, EXTEND(n, i) adaug n copii ale elementului de rang i (nu este valid pentru tipul index-by tables); LIMIT returneaz numrul maxim de elemente ale unei colecii (cel de la declarare) pentru tipul vector i null pentru tablouri imbricate (nu este valid pentru tipul index-by tables); TRIM terge elementele de la sfritul unei colecii: TRIM terge ultimul element, TRIM(n) terge ultimele n elemente (nu este valid pentru tipul index-by tables). Similar metodei EXTEND, metoda TRIM opereaz asupra dimensiunii interne a tabloului imbricat. EXISTS este singura metod care poate fi aplicat unei colecii atomice null.

Programare n PL/SQL

281

Orice alt metod declaneaz excepia COLLECTION_IS_NULL. COUNT, EXISTS, FIRST, LAST, NEXT, PRIOR i LIMIT sunt funcii, iar restul sunt proceduri PL/SQL.

Bulk bind
n exemplul care urmeaz, comanda DELETE este trimis motorului SQL pentru fiecare iteraie a comenzii FOR. Exemplu:
DECLARE TYPE nume IS VARRAY(20) OF NUMBER; alfa nume := nume(10,20,70); -- coduri ale galeriilor BEGIN FOR j IN alfa.FIRST..alfa.LAST LOOP DELETE FROM opera WHERE cod_galerie = alfa (j); END LOOP; END;

Pentru a realiza mai rapid aceast operaie, ar trebui s existe posibilitatea de a terge (prelucra) ntreaga colecie i nu elemente individuale. Mecanismul care permite acest lucru este cunoscut sub numele bulk bind. n timpul compilrii, motorul PL/SQL asociaz identificatoriilor o adres, un tip de date i o valoare. Acest proces este numit binding. Comenzile SQL din blocurile PL/SQL sunt trimise motorului SQL pentru a fi executate. Motorul SQL poate trimite date napoi motorului PL/SQL (de exemplu, ca rezultat al unei interogri). De multe ori, datele care trebuie prelucrate aparin unei colecii, iar colecia este iterat printr-un ciclu FOR. Prin urmare, transferul (n ambele sensuri) ntre SQL i PL/SQL are loc pentru fiecare linie a coleciei. ncepnd cu Oracle8i exist posibilitatea ca toate liniile unei colecii s fie transferate simultan printr-o singur operaie. Procedeul este numit bulk bind i este realizat cu ajutorul comenzii FORALL, ce poate fi folosit cu orice tip de colecie. Comanda FORALL are sintaxa: FORALL index IN lim_inf..lim_sup comanda_sql; Motorul SQL execut comanda_sql o singur dat pentru toate valorile indexului. Comanda_sql este una dintre instruciunile INSERT, UPDATE, DELETE care refer elementele uneia sau mai multor colecii. Variabila index poate fi referit numai n comanda FORALL i numai ca indice de colecie. Exemplul care urmeaz optimizeaz problema anterioar, n sensul c DELETE este trimis motorului SQL o singur dat, pentru toate liniile coleciei. Exemplu:
DECLARE TYPE nume IS VARRAY(20) OF NUMBER; alfa nume := nume(10,20,70); -- coduri ale galeriilor

282

PROGRAMARE AVANSAT N ORACLE9i

BEGIN FORALL j IN alfa.FIRST..alfa.LAST DELETE FROM opera WHERE cod_galerie = alfa (j); END;

Pentru utilizarea comenzii FORALL sunt necesare urmtoarele restricii: comanda poate fi folosit numai n programe server-side, altfel apare eroarea this feature is not supported in client-side programs; comenziile INSERT, UPDATE, DELETE trebuie s refere cel puin o colecie; toate elementele coleciei din domeniul precizat trebuie s existe (dac, de exemplu, un element a fost ters, atunci este semnalat o eroare); indicii coleciilor nu pot s fie expresii i trebuie s aib valori continue. Dac exist o eroare n procesarea unei linii printr-o operaie LMD de tip bulk, numai acea linie va fi derulat napoi (rollback). Cursorul SQL are un atribut compus, %BULK_ROWCOUNT , care numr liniile afectate de iteraiile comenzii FORALL. %BULK_ROWCOUNT (i) reprezint numrul de linii procesate de a i-a execuie a comenzii SQL. Atributul nu poate fi parametru ntr-un subprogram i nu poate fi asignat altei colecii. ncepnd cu Oracle9i, este inclus o nou clauz n comanda FORALL. Clauza, numit SAVE EXCEPTIONS, permite ca toate excepiile care apar n timpul execuiei comenzii FORALL s fie salvate i astfel procesarea poate s continue. n acest context, poate fi utilizat atributul cursor %BULK_EXCEPTIONS pentru a vizualiza informaii despre aceste excepii. Atributul acioneaz ca un tablou PL/SQL i are dou cmpuri: %BULK_EXCEPTIONS(i).ERROR_INDEX, reprezentnd iteraia n timpul creia s-a declanat excepia; %BULK_EXCEPTIONS(i).ERROR_CODE, reprezentnd codul Oracle al erorii respective. Regsirea rezultatului unei interogri n colecii (nainte de a fi trimis motorului PL/SQL) se poate obine cu ajutorul clauzei BULK COLLECT. Aceast clauz poate s apar n comenzile SELECT INTO (cursoare implicite), FETCH INTO (cursoare explicite) sau n clauza RETURNING INTO a comenzilor INSERT, UPDATE, DELETE. n cazul cursoarelor explicite, numrul liniilor ncrcate din baza de date poate fi limitat utiliznd opiunea LIMIT. Clauza BULK COLLECT are urmtoarea sintax: BULK COLLECT INTO nume_colecie [, nume_colecie] Exemplu:
DECLARE TYPE tip1 IS TABLE OF opera.cod_opera%TYPE; TYPE tip2 IS TABLE OF opera.titlu%TYPE; alfa tip1;

Programare n PL/SQL

283

beta tip2; BEGIN /* motorul SQL incarca in intregime coloanele cod_opera si titlu in tabelele imbricate, inainte de a returna tabelele motorului PL/SQL */ SELECT cod_opera, titlu BULK COLLECT INTO alfa,beta FROM opera; /* daca exista n opere de arta in stare buna, atunci alfa va contine codurile celor n opere de arta */ DELETE FROM opera WHERE stare = 'buna' RETURNING cod_opera BULK COLLECT INTO alfa; END;

Comanda FORALL se poate combina cu clauza BULK COLLECT . Totui, trebuie subliniat c ele nu pot fi folosite simultan n comanda SELECT.

5. Gestiunea cursoarelor
Pentru a procesa o comand SQL, sistemul Oracle folosete o zon de memorie cunoscut sub numele de zon context (context area). Cnd este procesat o instruciune SQL, server-ul Oracle deschide aceast zon de memorie n care comanda este analizat sintactic i executat. Zona conine informaii necesare procesrii comenzii, cum ar fi: numrul de rnduri procesate de instruciune; un pointer ctre reprezentarea intern a comenzii; n cazul unei cereri, mulimea rndurilor rezultate n urma execuiei acestei comenzi (active set). Un cursor este un pointer la aceast zon context. Prin intermediul cursoarelor, un program PL/SQL poate controla zona context i transformrile petrecute n urma procesrii comenzii. Exist dou tipuri de cursoare: implicite, generate de server-ul Oracle cnd n partea executabil a unui bloc PL/SQL apare o instruciune SQL; explicite, declarate i definite de ctre utilizator atunci cnd o cerere (SELECT), care apare ntr-un bloc PL/SQL, ntoarce mai multe linii ca rezultat. Att cursoarele implicite ct i cele explicite au o serie de atribute ale cror valori pot fi folosite n expresii. Lista atributelor este urmtoarea: %ROWCOUNT , care este de tip ntreg i reprezint numrul liniilor ncrcate de cursor; %FOUND, care este de tip boolean i ia valoarea TRUE dac ultima

284

PROGRAMARE AVANSAT N ORACLE9i

operaie de ncrcare (FETCH) dintr-un cursor a avut succes (n cazul cursoarelor explicite) sau dac instruciunea SQL a ntors cel puin o linie (n cazul cursoarelor implicite); %NOTFOUND, care este de tip boolean i are semnificaie opus fa de cea a atributului %FOUND; %ISOPEN, care este de tip boolean i indic dac un cursor este deschis (n cazul cursoarelor implicite, acest atribut are ntotdeauna valoarea FALSE, deoarece un cursor implicit este nchis de sistem imediat dup executarea instruciunii SQL asociate).

Atributele pot fi referite prin expresia SQL%nume_atribut, n cazul cursoarelor implicite, sau prin nume_cursor%nume_atribut, n cazul unui cursor explicit. Ele pot s apar n comenzi PL/SQL, n funcii, n seciunea de tratare a erorilor, dar nu pot fi utilizate n comenzi SQL.

Cursoare implicite
Cnd se proceseaz o comand LMD, motorul SQL deschide un cursor implicit. Atributele scalare ale cursorului implicit (SQL%ROWCOUNT , SQL %FOUND, SQL%NOTFOUND, SQL%ISOPEN) furnizeaz informaii referitoare la ultima comand INSERT, UPDATE, DELETE sau SELECT INTO executat. nainte ca Oracle s deschid cursorul SQL implicit, atributele acestuia au valoarea null. n Oracle9i, pentru cursoare implicite a fost introdus atributul compus %BULK_ROWCOUNT, care este asociat comenzii FORALL. Atributul are semantica unui tablou indexat. Componenta %BULK_ROWCOUNT(j) conine numrul de linii procesate de a j-a execuie a unei comenzi INSERT, DELETE sau UPDATE. Dac a j-a execuie nu afecteaz nici o linie, atunci atributul returneaz valoarea 0. Comanda FORALL i atributul %BULK_ROWCOUNT au aceiai indici, deci folosesc acelai domeniu. Dac %BULK_ROWCOUNT (j) este zero, atributul %FOUND este FALSE. Exemplu: n exemplul care urmeaz, comanda FORALL insereaz un numr arbitrar de linii la fiecare iteraie, iar dup fiecare iteraie atributul %BULK_ROWCOUNT returneaz numrul acestor linii inserate.
SET SERVEROUTPUT ON DECLARE TYPE alfa IS TABLE OF NUMBER; beta alfa; BEGIN SELECT cod_artist BULK COLLECT INTO beta FROM artist; FORALL j IN 1..beta.COUNT INSERT INTO tab_art SELECT cod_artist,cod_opera FROM opera WHERE cod_artist = beta(j); FOR j IN 1..beta.COUNT LOOP

Programare n PL/SQL

285

DBMS_OUTPUT.PUT_LINE ('Pentru artistul avand codul ' || beta(j) || ' au fost inserate ' || SQL%BULK_ROWCOUNT(j) || inregistrari (opere de arta)'); END LOOP; DBMS_OUTPUT.PUT_LINE ('Numarul total de inregistrari inserate este '||SQL%ROWCOUNT); END; / SET SERVEROUTPUT OFF

Cursoare explicite
Pentru gestiunea cursoarelor explicite sunt necesare urmtoarele etape: declararea cursorului (atribuirea unui nume i asocierea cu o comand SELECT); deschiderea cursorului pentru cerere (executarea interogrii asociate i determinarea mulimii rezultat); recuperarea liniilor rezultatului n variabile PL/SQL; nchiderea cursorului (eliberarea resurselor relative la cursor). Prin urmare, pentru a utiliza un cursor, el trebuie declarat n seciunea declarativ a programului, trebuie deschis n partea executabil, urmnd s fie utilizat apoi pentru extragerea datelor. Dac nu mai este necesar n restul programului, cursorul trebuie s fie nchis. DECLARE declarare cursor BEGIN deschidere cursor (OPEN) WHILE rmn linii de recuperat LOOP recuperare linie rezultat (FETCH ) END LOOP nchidere cursor (CLOSE) END; Pentru a controla activitatea unui cursor sunt utilizate comenzile DECLARE, OPEN, FETCH i CLOSE.

Declararea unui cursor explicit


Prin declaraia CURSOR n cadrul comenzii DECLARE este definit un cursor explicit i este precizat structura cererii care va fi asociat acestuia. Declaraia CURSOR are urmtoarea form sintactic: CURSOR nume_cursor IS comanda_select Identificatorul nume_cursor este numele cursorului, iar comanda_select este cererea SELECT care va fi procesat.

286
Observaii:

PROGRAMARE AVANSAT N ORACLE9i

Comanda SELECT care apare n declararea cursorului, nu trebuie s includ clauza INTO. Dac se cere procesarea liniilor ntr-o anumit ordine, atunci n cerere este utilizat clauza ORDER BY. Variabilele care sunt referite n comanda de selectare trebuie declarate naintea comenzii CURSOR. Ele sunt considerate variabile de legtur. Dac n lista comenzii SELECT apare o expresie, atunci pentru expresia respectiv trebuie utilizat un alias, iar cmpul expresie se va referi prin acest alias. Numele cursorului este un identificator unic n cadrul blocului, care nu poate s apar ntr-o expresie i cruia nu i se poate atribui o valoare.

Deschiderea unui cursor explicit


Comanda OPEN execut cererea asociat cursorului, identific mulimea liniilor rezultat i poziioneaz cursorul naintea primei linii. Deschiderea unui cursor se face prin comanda: OPEN nume_cursor; Identificatorul nume_cursor reprezint numele cursorului ce va fi deschis. La deschiderea unui cursor se realizeaz urmtoarele operaii: se evalueaz cererea asociat (sunt examinate valorile variabilelor de legtur ce apar n declaraia cursorului); este determinat mulimea rezultat (active set) prin executarea cererii SELECT, avnd n vedere valorile de la pasul anterior; pointer-ul este poziionat la prima linie din mulimea activ.

ncrcarea datelor dintr-un cursor explicit


Comanda FETCH regsete liniile rezultatului din mulimea activ. FETCH realizeaz urmtoarele operaii: avanseaz pointer-ul la urmtoarea linie n mulimea activ (pointer-ul poate avea doar un sens de deplasare de la prima spre ultima nregistrare); citete datele liniei curente n variabile PL/SQL; dac pointer-ul este poziionat la sfritul mulimii active atunci se iese din bucla cursorului. Comanda FETCH are urmtoarea sintax: FETCH nume_cursor INTO {nume_variabil [, nume_variabil] | nume_nregistrare}; Identificatorul nume_cursor reprezint numele unui cursor declarat i deschis anterior. Variabila sau lista de variabile din clauza INTO trebuie s fie compatibil (ca ordine i tip) cu lista selectat din cererea asociat cursorului. La un moment dat, comanda FETCH regsete o singur linie. Totui, n

Programare n PL/SQL

287

ultimele versiuni Oracle pot fi ncrcate mai multe linii (la un moment dat) ntr-o colecie, utiliznd clauza BULK COLLECT. Exemplu: n exemplul care urmeaz se ncarc date dintr-un cursor n dou colecii.
DECLARE TYPE TYPE cod1 titlu1 CURSOR ccopera IS TABLE OF opera.cod_opera%TYPE; ctopera IS TABLE OF opera.titlu%TYPE; ccopera; ctopera; alfa IS SELECT cod_opera, titlu FROM opera WHERE stil = 'impresionism';

BEGIN OPEN alfa; FETCH alfa BULK COLLECT INTO cod1, titlu1; CLOSE alfa; END;

nchiderea unui cursor explicit


Dup ce a fost procesat mulimea activ, cursorul trebuie nchis. Prin aceast operaie, PL/SQL este informat c programul a terminat folosirea cursorului i resursele asociate acestuia pot fi eliberate. Aceste resurse includ spaiul utilizat pentru memorarea mulimii active i spaiul temporar folosit pentru determinarea mulimii active. Cursorul va fi nchis prin comanda CLOSE, care are urmtoarea sintax: CLOSE nume_cursor; Identificatorul nume_cursor este numele unui cursor deschis anterior. Pentru a reutiliza cursorul este suficient ca acesta s fie redeschis. Dac se ncearc ncrcarea datelor dintr-un cursor nchis, atunci apare excepia INVALID_CURSOR. Un bloc PL/SQL poate s se termine fr a nchide cursoarele, dar acest lucru nu este indicat, deoarece este bine ca resursele s fie eliberate. Exemplu: Pentru toi artitii care au opere de art expuse n muzeu s se insereze n tabelul temp informaii referitoare la numele acestora i anul naterii.
DECLARE v_nume artist.nume%TYPE; v_an_nas artist.an_nastere%TYPE; CURSOR info IS SELECT DISTINCT nume, an_nastere FROM artist; BEGIN OPEN info; LOOP

288

PROGRAMARE AVANSAT N ORACLE9i

FETCH info INTO v_nume, v_an_nas; EXIT WHEN info%NOTFOUND; INSERT INTO temp VALUES (v_nume || TO_CHAR(v_an_nas)); END LOOP; CLOSE info; COMMIT; END;

Valorile atributelor unui cursor explicit sunt prezentate n urmtorul tabel:


OPEN Prima ncrcare Urmtoare a ncrcare Ultima ncrcare CLOSE nainte Dup nainte Dup nainte Dup nainte Dup nainte Dup %FOUND Excepie Null Null True True True True False False Excepie %ISOPEN False True True True True True True True True False %NOTFOUND Excepie Null Null False False False False True True Excepie %ROWCOUNT Excepie 0 0 1 1 Depinde de date Depinde de date Depinde de date Depinde de date Excepie

Dup prima ncrcare, dac mulimea rezultat este vid, %FOUND va fi FALSE, %NOTFOUND va fi TRUE, iar %ROWCOUNT este 0. ntr-un pachet poate fi separat specificarea unui cursor de corpul acestuia. Cursorul va fi declarat n specificaia pachetului prin comanda: CURSOR nume_cursor [ (parametru [, parametru]) ] RETURN tip_returnat; n felul acesta va crete flexibilitatea programului, putnd fi modificat doar corpul cursorului, fr a schimba specificaia. Exemplu:
CREATE PACKAGE exemplu AS CURSOR alfa (p_valoare_min NUMBER) RETURN opera%ROWTYPE; -- declaratie specificatie cursor END exemplu; CREATE PACKAGE BODY exemplu AS CURSOR alfa (p_valoare_min NUMBER) RETURN opera%ROWTYPE IS SELECT * FROM opera WHERE valoare > p_valoare_min; -- definire corp cursor END exemplu;

Programare n PL/SQL

289

Procesarea liniilor unui cursor explicit


Pentru procesarea diferitelor linii ale unui cursor explicit se folosete operaia de ciclare (LOOP, WHILE, FOR), prin care la fiecare iteraie se va ncrca o nou linie. Comanda EXIT poate fi utilizat pentru ieirea din ciclu, iar valoarea atributului %ROWCOUNT pentru terminarea ciclului. Procesarea liniilor unui cursor explicit se poate realiza i cu ajutorul unui ciclu FOR special, numit ciclu cursor. Pentru acest ciclu este necesar doar declararea cursorului, operaiile de deschidere, ncrcare i nchidere ale acestuia fiind implicite. Comanda are urmtoarea sintax: FOR nume_nregistrare IN nume_cursor LOOP secven_de_instruciuni; END LOOP; Variabila nume_nregistrare (care controleaz ciclul) nu trebuie declarat. Domeniul ei este doar ciclul respectiv. Pot fi utilizate cicluri cursor speciale care folosesc subcereri, iar n acest caz nu mai este necesar nici declararea cursorului. Exemplul care urmeaz este concludent n acest sens. Exemplu: S se calculeze, utiliznd un ciclu cursor cu subcereri, valoarea operelor de art expuse ntr-o galerie al crei cod este introdus de la tastatur. De asemenea, s se obin media valorilor operelor de art expuse n galeria respectiv.
SET SERVEROUTPUT ON ACCEPT p_galerie PROMPT 'Dati codul galeriei:' DECLARE v_cod_galerie galerie.cod_galerie%TYPE:=&p_galerie; val NUMBER; media NUMBER; i INTEGER; BEGIN val:=0; i:=0; FOR numar_opera IN (SELECT cod_opera, valoare FROM opera WHERE cod_galerie = v_cod_galerie) LOOP val := val + numar_opera.valoare; i := i+1; END LOOP;--nchidere implicit DBMS_OUTPUT.PUT_LINE('Valoarea operelor de arta din galeria cu numarul ' || TO_CHAR(v_cod_galerie) || ' este ' || TO_CHAR(val)); IF i=0 THEN

290

PROGRAMARE AVANSAT N ORACLE9i

DBMS_OUTPUT.PUT_LINE('Galeria nu are opere de arta'); ELSE media := val/i; DBMS_OUTPUT.PUT_LINE('Media valorilor operelor de arta din galeria cu numarul ' || TO_CHAR(v_cod_galerie) || ' este ' || TO_CHAR(media)); END IF; END; / SET SERVEROUTPUT OFF

Cursoare parametrizate
Unei variabile de tip cursor i corespunde o comand SELECT, care nu poate fi schimbat pe parcursul programului. Pentru a putea lucra cu nite cursoare ale cror comenzi SELECT ataate depind de parametri ce pot fi modificai la momentul execuiei, n PL/SQL s-a introdus noiunea de cursor parametrizat. Prin urmare, un cursor parametrizat este un cursor n care comanda SELECT ataat depinde de unul sau mai muli parametri. Transmiterea de parametri unui cursor parametrizat se face n mod similar procedurilor stocate. Un astfel de cursor este mult mai uor de interpretat i de ntreinut, oferind i posibilitatea reutilizrii sale n blocul PL/SQL. Declararea unui astfel de cursor se face respectnd urmtoarea sintax: CURSOR nume_cursor [ (nume_parametru[, nume_parametru ] ) ] [RETURN tip_returnat] IS comanda_select; Identificatorul comanda_select este o instruciune SELECT fr clauza INTO, tip_returnat reprezint un tip nregistrare sau linie de tabel, iar nume_parametru are sintaxa: nume_parametru [IN] tip_parametru [ {:= | DEFAULT} expresie] n aceast declaraie, atributul tip_parametru reprezint tipul parametrului, care este un tip scalar. Parametrii formali sunt de tip IN i, prin urmare, nu pot returna valori parametrilor actuali. Ei nu suport constrngerea NOT NULL. Deschiderea unui astfel de cursor se face asemntor apelului unei funcii, specificnd lista parametrilor actuali ai cursorului. n determinarea mulimii active se vor folosi valorile actuale ale acestor parametri. Sintaxa pentru deschiderea unui cursor parametrizat este: OPEN nume_cursor [ (valoare_parametru [, valoare_parametru] ) ]; Parametrii sunt specificai similar celor de la subprograme. Asocierea dintre parametrii formali i cei actuali se face prin: poziie parametrii formali i actuali sunt separai prin virgul;

Programare n PL/SQL

291

nume parametrii actuali sunt aranjai ntr-o ordine arbitrar, dar cu o coresponden de forma parametru formal => parametru actual.

Dac n definiia cursorului, toi parametrii au valori implicite (DEFAULT), cursorul poate fi deschis fr a specifica vreun parametru. Exemplu: Utiliznd un cursor parametrizat s se obin codurile operelor de art din fiecare sal, identificatorul slii i al galeriei. Rezultatele s fie inserate n tabelul mesaje.
DECLARE v_cod_sala sala.cod_sala%TYPE; v_cod_galerie galerie.cod_galerie%TYPE; v_car VARCHAR2(75); CURSOR sala_cursor IS SELECT cod_sala,cod_galerie FROM sala; CURSOR ope_cursor (v_id_sala NUMBER,v_id_galerie NUMBER) IS SELECT cod_opera || cod_sala || cod_galerie FROM opera WHERE cod_sala = v_id_sala AND cod_galerie = v_id_galerie; BEGIN OPEN sala_cursor; LOOP FETCH sala_cursor INTO v_cod_sala,v_cod_galerie; EXIT WHEN sala_cursor%NOTFOUND; IF ope_cursor%ISOPEN THEN CLOSE ope_cursor; END IF; OPEN ope_cursor (v_cod_sala, v_cod_galerie); LOOP FETCH ope_cursor INTO v_car; EXIT WHEN ope_cursor%NOTFOUND; INSERT INTO mesaje (rezultat) VALUES (v_car); END LOOP; CLOSE ope_cursor; END LOOP; CLOSE sala_cursor; COMMIT; END;

Cursoare SELECT FOR UPDATE


Uneori este necesar blocarea liniilor nainte ca acestea s fie terse sau reactualizate. Blocarea se poate realiza (atunci cnd cursorul este deschis) cu ajutorul comenzii SELECT care conine clauza FOR UPDATE. Declararea unui astfel de cursor se face conform sintaxei:

292

PROGRAMARE AVANSAT N ORACLE9i

CURSOR nume_cursor IS comanda_select FOR UPDATE [OF lista_cmpuri] [NOWAIT]; Identificatorul lista_cmpuri este o list ce include cmpurile tabelului care vor fi modificate. Atributul NOWAIT returneaz o eroare dac liniile sunt deja blocate de alt sesiune. Liniile unui tabel sunt blocate doar dac clauza FOR UPDATE se refer la coloane ale tabelului respectiv. n momentul deschiderii unui astfel de cursor, liniile corespunztoare mulimii active, determinate de clauza SELECT, sunt blocate pentru operaii de scriere (reactualizare sau tergere). n felul acesta este realizat consistena la citire a sistemului. De exemplu, aceast situaie este util cnd se reactualizeaz o valoare a unei linii i trebuie avut sigurana c linia nu este schimbat de alt utilizator naintea reactualizrii. Prin urmare, alte sesiuni nu pot schimba liniile din mulimea activ pn cnd tranzacia nu este permanentizat sau anulat. Dac alt sesiune a blocat deja liniile din mulimea activ, atunci comanda SELECT FOR UPDATE va atepta (sau nu) ca aceste blocri s fie eliberate. Pentru a trata aceast situaie se utilizeaz clauza WAIT, respectiv NOWAIT. n Oracle9i este utilizat sintaxa: SELECT FROM FOR UPDATE [OF lista_campuri] [ {WAIT n | NOWAIT} ]; Valoarea lui n reprezint numrul de secunde de ateptare. Dac liniile nu sunt deblocate n n secunde, atunci se declaneaz eroarea ORA-30006, respectiv eroarea ORA-00054, dup cum este specificat clauza WAIT, respectiv NOWAIT. Dac nu este specificat nici una din clauzele WAIT sau NOWAIT, sistemul ateapt pn ce linia este deblocat i atunci returneaz rezultatul comenzii SELECT. Dac un cursor este declarat cu clauza FOR UPDATE, atunci comenzile DELETE i UPDATE corespunztoare trebuie s conin clauza WHERE CURRENT OF nume_cursor. Aceast clauz refer linia curent care a fost gsit de cursor, permind ca reactualizrile i tergerile s se efectueze asupra acestei linii, fr referirea explicit a cheii primare sau pseudocoloanei ROWID. De subliniat c instruciunile UPDATE i DELETE vor reactualiza numai coloanele listate n clauza FOR UPDATE. Pseudocoloana ROWID poate fi utilizat dac tabelul referit n interogare nu are o cheie primar specificat. ROWID-ul fiecrei linii poate fi ncrcat ntr-o variabil PL/SQL (declarat de tipul ROWID sau UROWID), iar aceast variabil poate fi utilizat n clauza WHERE (WHERE ROWID = v_rowid). Dup nchiderea cursorului este necesar comanda COMMIT pentru a realiza scrierea efectiv a modificrilor, deoarece cursorul lucreaz doar cu nite copii ale liniilor reale existente n tabele. Deoarece blocrile implicate de clauza FOR UPDATE vor fi eliberate de comanda COMMIT, nu este recomandat utilizarea comenzii COMMIT n interiorul ciclului n care se fac ncrcri de date. Orice FETCH executat dup COMMIT va eua. n cazul n care cursorul nu este definit prin SELECTFOR UPDATE, nu

Programare n PL/SQL

293

sunt probleme n acest sens i, prin urmare, n interiorul ciclului unde se fac schimbri ale datelor poate fi utilizat un COMMIT. Exemplu: S se dubleze valoarea operelor de art pictate pe pnz care au fost achiziionate nainte de 1 ianuarie 1956.
DECLARE CURSOR calc IS SELECT * FROM opera WHERE material = 'panza' AND data_achizitie <= TO_DATE('01-JAN-56','DD-MON-YY') FOR UPDATE OF valoare NOWAIT; BEGIN FOR x IN calc LOOP UPDATE opera SET valoare = valoare*2 WHERE CURRENT OF calc; END LOOP; -- se permanentizeaza actiunea si se elibereaza blocarea COMMIT; END;

Cursoare dinamice
Toate exemplele considerate anterior se refer la cursoare statice. Unui cursor static i se asociaz o comand SQL care este cunoscut n momentul n care blocul este compilat. n PL/SQL a fost introdus variabila cursor, care este de tip referin. Variabilele cursor sunt similare tipului pointer din limbajele C sau Pascal. Prin urmare, un cursor este un obiect static, iar un cursor dinamic este un pointer la un cursor. n momentul declarrii, variabilele cursor nu solicit o comand SQL asociat. n acest fel, diferite comenzi SQL pot fi asociate variabilelor cursor, la diferite momente de timp. Acest tip de variabil trebuie declarat, deschis, ncrcat i nchis n mod similar unui cursor static. Variabilele cursor sunt dinamice deoarece li se pot asocia diferite interogri atta timp ct coloanele returnate de fiecare interogare corespund declaraiei variabilei cursor. Aceste variabile sunt utile n transmiterea seturilor de rezultate ntre subprograme PL/SQL stocate i diferii clieni. De exemplu, un client OCI, o aplicaie Oracle Forms i server-ul Oracle pot referi aceeai zon de lucru (care conine mulimea rezultat). Pentru a reduce traficul n reea, o variabil cursor poate fi declarat pe staia client, deschis i se pot ncrca date din ea pe server, apoi poate continua ncrcarea, dar de pe staia client etc. Pentru a crea o variabil cursor este necesar definirea unui tip REF

294

PROGRAMARE AVANSAT N ORACLE9i

CURSOR, urmnd apoi declararea unei variabile de tipul respectiv. Dup ce variabila cursor a fost declarat, ea poate fi deschis pentru orice cerere SQL care returneaz date de tipul declarat. Sintaxa pentru declararea variabilei cursor este urmtoarea: TYPE tip_ref_cursor IS REF CURSOR [RETURN tip_returnat]; var_cursor tip_ref_cursor; Identificatorul var_cursor este numele variabilei cursor, tip_ref_cursor este un nou tip de dat ce poate fi utilizat n declaraiile urmtoare ale variabilelor cursor, iar tip_returnat este un tip nregistrare sau tipul unei linii dintr-un tabel al bazei. Acest tip corespunde coloanelor returnate de ctre orice cursor asociat variabilelor cursor de tipul definit. Dac lipsete clauza RETURN, cursorul poate fi deschis pentru orice cerere SELECT. Dac variabila cursor apare ca parametru ntr-un subprogram, atunci trebuie specificat tipul parametrului (tipul REF CURSOR) i forma acestuia (IN sau IN OUT). Exist anumite restricii referitoare la utilizarea variabilelor cursor: nu pot fi declarate ntr-un pachet; cererea asociat variabilei cursor nu poate include clauza FOR UPDATE (restricia dispare n Oracle9i); nu poate fi asignat valoarea null unei variabile cursor; nu poate fi utilizat tipul REF CURSOR pentru a specifica tipul unei coloane n comanda CREATE TABLE; nu pot fi utilizai operatorii de comparare pentru a testa egalitatea, inegalitatea sau valoarea null a variabilelor cursor; nu poate fi utilizat tipul REF CURSOR pentru a specifica tipul elementelor unei colecii (varray, nested table); nu pot fi folosite cu SQL dinamic n Pro*C/C++. n cazul variabilelor cursor, instruciunile de deschidere (OPEN), ncrcare (FETCH), nchidere (CLOSE) vor avea o sintax similar celor comentate anterior. Comanda OPENFOR asociaz o variabil cursor cu o cerere multilinie, execut cererea, identific mulimea rezultat i poziioneaz cursorul la prima linie din mulimea rezultat. Sintaxa comenzii este: OPEN {variabila_cursor | :variabila_cursor_host} FOR {cerere_select | ir_dinamic [USING argument_bind [, argument_bind ] ] }; Identificatorul variabila_cursor specific o variabil cursor declarat anterior, dar fr opiunea RETURN tip, cerere_select este interogarea pentru care este deschis variabila cursor, iar ir_dinamic este o secven de caractere care reprezint cererea multilinie. Opiunea ir_dinamic este specific prelucrrii dinamice a comenzilor, iar posibilitile oferite de SQL dinamic vor fi analizate ntr-un capitol separat. Identificatorul :variabila_cursor_host reprezint o variabil cursor declarat ntr-un

Programare n PL/SQL

295

mediu gazd PL/SQL (de exemplu, un program OCI). Comanda OPENFOR poate deschide acelai cursor pentru diferite cereri. Nu este necesar nchiderea variabilei cursor nainte de a o redeschide. Dac se redeschide variabila cursor pentru o nou cerere, cererea anterioar este pierdut. Exemplu:
CREATE OR REPLACE PACKAGE alfa AS TYPE ope_tip IS REF CURSOR RETURN opera%ROWTYPE; PROCEDURE deschis_ope (ope_var IN OUT ope_tip, alege IN NUMBER); END alfa; CREATE OR REPLACE PACKAGE BODY alfa AS PROCEDURE deschis_ope (ope_var IN OUT ope_tip, alege IN NUMBER) IS BEGIN IF alege = 1 THEN OPEN ope_var FOR SELECT * FROM opera; ELSIF alege = 2 THEN OPEN ope_var FOR SELECT * FROM opera WHERE valoare > 200; ELSIF alege = 3 THEN OPEN ope_var FOR SELECT * FROM opera WHERE valoare = 777; END IF; END deschis_ope; END alfa;

Comanda FETCH returneaz o linie din mulimea rezultat a cererii, atribuie valorile returnate de cerere componentelor din lista specificat prin clauza INTO i avanseaz cursorul la urmtoarea linie. Comanda are urmtoarea sintax: FETCH {variabila_cursor | :variabila_cursor_host} INTO {variabila [, variabila ] | nregistrare} [BULK COLLECT INTO {nume_colecie [, nume_colecie ] } | {nume_array_host [, nume_array_host ] } [LIMIT expresie_numerica] ]; Clauza BULK COLLECT INTO permite ncrcarea tuturor liniilor simultan n una sau mai multe colecii. Atributul nume_colecie indic o colecie declarat anterior, n care sunt depuse valorile respective, iar nume_array_host identific un vector declarat ntr-un mediu gazd PL/SQL i trimis lui PL/SQL ca variabil de legtur. Prin clauza LIMIT se limiteaz numrul liniilor ncrcate din baza de date. Exemplu:
DECLARE TYPE alfa IS REF CURSOR RETURN opera%ROWTYPE; TYPE beta IS TABLE OF opera.titlu%TYPE; TYPE gama IS TABLE OF opera.valoare%TYPE; var1 alfa; var2 beta; var3 gama;

296

PROGRAMARE AVANSAT N ORACLE9i

BEGIN OPEN var1 FOR SELECT titlu, valoare FROM opera; FETCH var1 BULK COLLECT INTO var2, var3; CLOSE var1; END;

Comanda CLOSE dezactiveaz variabila cursor precizat. Ea are sintaxa: CLOSE {variabila_cursor | :variabila_cursor_host} Cursoarele i variabilele cursor nu sunt interoperabile. Nu poate fi folosit una dintre ele, atunci cnd este ateptat cealalt.

Expresie cursor
n Oracle9i a fost introdus conceptul de expresie cursor (cursor expression), care returneaz un cursor imbricat (nested cursor). Expresia cursor are urmtoarea sintax: CURSOR (subcerere) Fiecare linie din mulimea rezultat poate conine valori uzuale i cursoare generate de subcereri. PL/SQL accept cereri care au expresii cursor n cadrul unei declaraii cursor, declaraii REF CURSOR i a variabilelor cursor. Prin urmare, expresia cursor poate s apar ntr-o comand SELECT ce este utilizat pentru deschiderea unui cursor dinamic. De asemenea, expresiile cursor pot fi folosite n cereri SQL dinamice sau ca parametri actuali ntr-un subprogram. Un cursor imbricat este ncrcat automat atunci cnd liniile care l conin sunt ncrcate din cursorul printe. El este nchis dac: este nchis explicit de ctre utilizator; cursorul printe este reexecutat, nchis sau anulat; apare o eroare n timpul unei ncrcri din cursorul printe. Exist cteva restricii asupra folosirii unei expresii cursor: nu poate fi utilizat cu un cursor implicit; poate s apar numai ntr-o comand SELECT care nu este imbricat n alt cerere (exceptnd cazul n care este o subcerere chiar a expresiei cursor) sau ca argument pentru funcii tabel, n clauza FROM a lui SELECT; nu poate s apar n interogarea ce definete o vizualizare; nu se pot efectua operaii BIND sau EXECUTE cu aceste expresii. Exemplu: S se defineasc un cursor care furnizeaz codurile operelor expuse n cadrul unei expoziii avnd un cod specificat (val_cod) i care se desfoar ntr-o localitate precizat (val_oras). S se afieze data cnd a avut loc vernisajul acestei expoziii. n acest caz cursorul returneaz dou coloane, cea de-a doua coloan fiind

Programare n PL/SQL

297

un cursor imbricat.
CURSOR alfa (val_cod NUMBER, val_oras VARCHAR2(20)) IS SELECT l.datai, CURSOR (SELECT d.cod_expo, CURSOR (SELECT f.cod_opera FROM figureaza_in f WHERE f.cod_expo=d.cod_expo) AS xx FROM expozitie d WHERE l.cod_expo = d.cod_expo) AS yy FROM locped l WHERE cod_expo = val_cod AND nume_oras= val_oras;

Exemplu: S se listeze numele galeriilor din muzeu i pentru fiecare galerie s se afieze numele slilor din galeria respectiv. Sunt prezentate dou variante de rezolvare. Prima variant reprezint o implementare simpl utiliznd programarea secvenial clasic, iar a doua utilizeaz expresii cursor pentru rezolvarea acestei probleme. Varianta 1:
BEGIN FOR gal IN (SELECT cod_galerie, nume_galerie FROM galerie) LOOP DBMS_OUTPUT.PUT_LINE (gal.nume_galerie); FOR sal IN (SELECT cod_sala, nume_sala FROM sala WHERE cod_galerie = gal.cod.galerie) LOOP DBMS_OUTPUT.PUT_LINE (sal.nume_sala); END LOOP; END LOOP; END;

Varianta 2:
DECLARE CURSOR c_gal IS SELECT nume_galerie, CURSOR (SELECT nume_sala FROM sala s WHERE s.cod_galerie = g.cod_galerie) FROM galerie g; v_nume_gal galerie.nume_galerie%TYPE; v_sala SYS.REFCURSOR; TYPE sala_nume IS TABLE OF sala.nume_sala%TYPE INDEX BY BINARY_INTEGER; v_nume_sala sala_nume; BEGIN OPEN c_gal;

298

PROGRAMARE AVANSAT N ORACLE9i

LOOP FETCH c_gal INTO v_nume_gal, v_sala; EXIT WHEN c_gal%NOTFOUND; DBMS_OUTPUT.PUT_LINE (v_nume_gal); FETCH v_sala BULK COLLECT INTO v_nume_sala; FOR ind IN v_nume_sala.FIRST..v_nume_sala.LAST LOOP DBMS_OUTPUT.PUT_LINE (v_nume_sala (ind)); END LOOP; END LOOP; CLOSE c_gal; END;

5. Modularizarea aplicaiilor prin utilizarea subprogramelor


Noiunea de subprogram (procedur sau funcie) a fost conceput cu scopul de a grupa o mulime de comenzi SQL cu instruciuni procedurale, pentru a construi o unitate logic de tratare a unei anumite probleme. n general, procedurile sunt folosite pentru a realiza o aciune, iar funciile pentru a calcula o valoare. Unitile de program care pot fi create n PL/SQL sunt: subprograme locale (definite n partea declarativ a unui bloc PL/SQL sau a unui alt subprogram); subprograme independente (stocate n baza de date i considerate obiecte ale acesteia); subprograme mpachetate (definite ntr-un pachet care ncapsuleaz proceduri i funcii). Procedurile i funciile stocate sunt uniti de program PL/SQL apelabile (compilate), care exist ca obiecte n schema bazei de date Oracle. Recuperarea unui subprogram (n cazul unei corecii) nu cere recuperarea ntregii aplicaii. Subprogramul, ncrcat n memorie pentru a fi executat, poate fi partajat ntre aplicaiile care l solicit. Este important de fcut distincie ntre procedurile stocate i procedurile locale (declarate i folosite n blocuri anonime). Procedurile i funciile stocate, care sunt compilate i stocate n baza de date, nu mai trebuie s fie compilate la fiecare execuie, n timp ce procedurile locale sunt compilate de fiecare dat cnd este executat blocul care le conine. Procedurile declarate i apelate n blocuri anonime sunt temporare (ele nu mai exist dup ce blocul anonim a fost executat complet). O procedur stocat (creat cu CREATE PROCEDURE sau coninut

Programare n PL/SQL

299

ntr-un pachet) este permanent, n sensul c ea poate fi invocat de un fiier SQL*Plus, un subprogram PL/SQL sau un declanator. Procedurile i funciile stocate pot fi apelate din orice bloc, de ctre utilizatorul care are privilegiul EXECUTE asupra acestora, n timp ce procedurile i funciile locale pot fi apelate numai din blocul care le conine.

Cnd este creat un subprogram stocat, utiliznd comanda CREATE, subprogramul este depus n dicionarul datelor. Este depus att textul surs, ct i forma compilat (p-code). Atunci cnd subprogramul este apelat, p-code este citit de pe disc, este depus n shared pool, unde poate fi accesat de mai muli utilizatori i este executat dac este necesar. El va prsi shared pool conform algoritmului LRU (least recently used). Pachetul DBMS_SHARED_POOL permite pstrarea de obiecte n shared pool. Dac un obiect este gestionat n aceast manier, el va prsi shared pool doar dac aceasta se cere explicit. Pentru a pstra n shared pool subprograme, pachete, declanatori, cursoare, clase Java, tipuri obiect, comenzi SQL, secvene este utilizat procedura DBMS_SHARED_POOL.KEEP. Unica modalitate de a terge un obiect pstrat n shared pool, fr a reporni baza, este cu ajutorul procedurii DBMS_SHARED_POOL.UNKEEP. Subprogramele se pot declara n blocuri PL/SQL, n alte subprograme sau n pachete, dar la sfritul seciunii declarative. La fel ca blocurile PL/SQL anonime, subprogramele conin o parte declarativ, o parte executabil i, opional, o parte de tratare a erorilor. Partea declarativ conine declaraii de tipuri, cursoare, constante, variabile, excepii i subprograme imbricate. Partea executabil conine instruciuni care asigneaz valori, controleaz execuia programului i prelucreaz datele. Cea de-a treia parte se ocup cu tratarea excepiilor aprute n timpul execuiei subprogramului.

Crearea subprogramelor stocate


Principalele etape pentru crearea unui subprogram stocat sunt urmtoarele: se editeaz subprogramul (CREATE PROCEDURE sau CREATE FUNCTION) i se salveaz ntr-un script file SQL; se ncarc i se execut acest script file, se compileaz codul surs, se obine p-code (subprogramul este creat); se utilizeaz comanda SHOW ERRORS pentru vizualizarea eventualelor erori la compilare (comanda CREATE PROCEDURE sau CREATE FUNCTION depune codul surs n dicionarul datelor chiar dac subprogramul conine erori la compilare); se execut subprogramul pentru a realiza aciunea dorit (de exemplu, procedura poate fi executat fie utiliznd comanda EXECUTE din iSQL*Plus, fie invocnd-o dintr-un bloc PL/SQL). Cnd este apelat subprogramul, motorul PL/SQL execut p-code. Dac exist erori la compilare i se fac coreciile corespunztoare, atunci

300

PROGRAMARE AVANSAT N ORACLE9i

este necesar fie comanda DROP PROCEDURE (respectiv DROP FUNCTION), fie sintaxa OR REPLACE n cadrul comenzii CREATE. Atunci cnd este apelat o procedur PL/SQL, server-ul Oracle parcurge anumite etape care vor fi detaliate n cele ce urmeaz. Verific dac utilizatorul are privilegiul s execute procedura (fie pentru c el a creat procedura, fie pentru c i s-a acordat acest privilegiu). Verific dac procedura este prezent n shared pool. Dac este prezent va fi executat, altfel va fi ncrcat de pe disc n database buffer cache. Verific dac starea procedurii este valid (VALID) sau invalid (INVALID). Starea unei proceduri PL/SQL este invalid, fie pentru c au fost detectate erori la compilarea procedurii, fie pentru c structura unui obiect s-a schimbat de cnd procedura a fost executat ultima oar. Dac starea este invalid atunci procedura este recompilat automat. Dac nici o eroare nu a fost detectat, atunci va fi executat noua versiune a procedurii. Dac procedura aparine unui pachet atunci toate procedurile i funciile pachetului sunt de asemenea ncrcate n database buffer cache (dac nu erau deja acolo). Dac pachetul este activat pentru prima oar ntr-o sesiune, atunci server-ul va executa blocul de iniializare al pachetului.

Proceduri PL/SQL
O procedur PL/SQL este un program independent care se afl compilat n schema bazei de date Oracle. Cnd procedura este compilat, identificatorul acesteia (stabilit prin comanda CREATE PROCEDURE) devine un nume de obiect n dicionarul datelor. Tipul obiectului este PROCEDURE. Sintaxa general pentru crearea unei proceduri este urmtoarea: [CREATE [OR REPLACE] ] PROCEDURE nume_procedur [ (parametru [, parametru ] ) ] [AUTHID {DEFINER | CURRENT_USER }] {IS | AS} [PRAGMA AUTONOMOUS_TRANSACTION;] [declaraii locale] BEGIN partea executabil [EXCEPTION partea de tratare a excepiilor] END [nume_procedur]; Parametrii au urmtoarea form sintactic: nume_parametru [ {IN | OUT [NOCOPY] | IN OUT [NOCOPY] } ] tip_de_date [ {:= | DEFAULT} expresie]

Programare n PL/SQL

301

Comanda CREATE permite ca procedura s fie stocat n baza de date. Cnd procedurile sunt create folosind clauza CREATE OR REPLACE, ele vor fi stocate n baza de date n form compilat, form care permite execuia mai rapid a acestora. Dac procedura exist, atunci clauza OR REPLACE va avea ca efect tergerea procedurii i nlocuirea acesteia cu noua versiune. Dac procedura exist, iar opiunea OR REPLACE nu este prezent, atunci comanda CREATE va returna eroarea ORA-00955: Name is already used by an existing object. Clauza AUTHID specific faptul c procedura stocat se execut cu drepturile proprietarului (implicit) sau ale utilizatorului curent. De asemenea, aceast clauz precizeaz dac referinele la obiecte sunt rezolvate n schema proprietarului procedurii sau n cea a utilizatorului curent. Clauza PRAGMA_AUTONOMOUS_TRANSACTION anun compilatorul PL/SQL c aceast procedur este autonom (independent). Tranzaciile autonome permit suspendarea tranzaciei principale, executarea unor instruciuni SQL, permanentizarea sau anularea acestor operaii i continuarea tranzaciei principale. Parametrii formali (variabile declarate n lista parametrilor specificaiei subprogramului) pot s fie de tipul %TYPE, %ROWTYPE sau de un tip explicit, fr specificarea dimensiunii. Exemplu: S se creeze o procedur stocat care micoreaz cu o valoare dat ( cant) poliele de asigurare emise de firma SALVAL.
CREATE OR BEGIN UPDATE SET WHERE END; / REPLACE PROCEDURE mic (cant IN NUMBER) AS politaasig valoare = valoare - cant firma = 'SALVAL';

Dac n subprograme se execut operaii de reactualizare i exist declanatori relativ la aceste operaii care nu trebuie s se execute, atunci nainte de apelarea subprogramului declanatorii trebuie dezactivai, urmnd ca ei s fie reactivai dup ce s-a terminat execuia subprogramului. De exemplu, n problema prezentat anterior ar trebui dezactivai declanatorii referitori la tabelul politaasig, apelat procedura mic i n final, reactivai aceti declanatori.
ALTER TABLE politaasig DISABLE ALL TRIGGERS; EXECUTE mic(10000) ALTER TABLE politaasig ENABLE ALL TRIGGERS;

Exemplu: S se creeze o procedur local prin care se insereaz informaii n tabelul editata_de.
DECLARE PROCEDURE editare (v_cod_sursa

editata_de.cod_sursa%TYPE,

302

PROGRAMARE AVANSAT N ORACLE9i

v_cod_autor editata_de.cod_autor%TYPE) IS BEGIN INSERT INTO editata_de VALUES (v_cod_sursa,v_cod_autor); END; BEGIN editare(75643, 13579); END; /

Procedurile stocate pot fi apelate: din corpul altei proceduri sau al unui declanator; interactiv, de ctre utilizator, folosind un instrument Oracle (de exemplu, iSQL*Plus); explicit dintr-o aplicaie (de exemplu, Oracle Forms sau prin utilizarea de precompilatoare). Apelarea unei proceduri se poate face n funcie de mediul care o solicit: 1) n SQL*Plus, prin comanda EXECUTE nume_procedur [ (lista_parametri_actuali) ];

2) n PL/SQL, prin invocarea numelui procedurii urmat de lista


parametrilor actuali. Parametrii actuali sunt variabile sau expresii referite n lista parametrilor subprogramului apelant. Ei trebuie s fie compatibili ca tip i numr cu parametrii formali.

Funcii PL/SQL
O funcie PL/SQL este similar unei proceduri cu excepia c ea trebuie s ntoarc un rezultat. O funcie fr comanda RETURN va genera o eroare la compilare. Cnd funcia este compilat, identificatorul acesteia devine obiect n dicionarul datelor avnd tipul FUNCTION. Algoritmul din interiorul corpului subprogramului funcie trebuie s asigure faptul c toate traiectoriile sale conduc la comanda RETURN. Dac o traiectorie a algoritmului trimite n partea de tratare a erorilor, atunci handler-ul acesteia trebuie s includ o comand RETURN. Orice funcie trebuie s conin clauza RETURN n antet i cel puin o comand RETURN n partea executabil. Sintaxa simplificat pentru scrierea unei funcii este urmtoarea: [CREATE [OR REPLACE] ] FUNCTION nume_funcie [ (parametru [, parametru ] ) ] RETURN tip_de_date [AUTHID {DEFINER | CURRENT_USER } ]

Programare n PL/SQL

303

[DETERMINISTIC] {IS | AS} [PRAGMA AUTONOMOUS_TRANSACTION;] [declaraii locale] BEGIN partea executabil [EXCEPTION partea de tratare a excepiilor] END [nume_funcie]; Opiunea tip_de_date specific tipul valorii returnate de funcie, tip care nu poate conine specificaii de dimensiune. Dac totui sunt necesare aceste specificaii, se pot defini subtipuri, iar parametrii i valoarea returnat vor fi declarai de acel subtip. n interiorul funciei trebuie s apar instruciunea RETURN expresie, unde expresie este valoarea rezultatului furnizat de funcie. Pot s fie mai multe comenzi RETURN ntr-o funcie, dar numai una din ele va fi executat. Comanda RETURN (fr o expresie asociat) poate s apar i ntr-o procedur. n acest caz, ea va avea ca efect saltul la comanda ce urmeaz instruciunii apelante. Opiunea DETERMINISTIC ajut optimizorul Oracle n cazul unor apeluri repetate ale aceleai funcii, cu aceleai argumente. Ea indic posibilitatea folosirii unui rezultat obinut anterior. Observaii:

n blocul PL/SQL al unei funcii stocate (cel care definete aciunea efectuat de funcie) nu pot fi referite variabile host sau variabile bind. O funcie poate accepta unul sau mai muli parametri, dar trebuie s returneze o singur valoare. Ca i n cazul procedurilor, lista parametrilor este opional. Dac subprogramul nu are parametri, parantezele nu sunt necesare la declarare i la apelare. O procedur care conine un parametru de tip OUT poate fi rescris sub forma unei funcii.

Exemplu: S se creeze o funcie stocat care determin numrul operelor de art realizate pe pnz, ce au fost achiziionate la o anumit dat.
CREATE OR REPLACE FUNCTION numar_opere (v_a IN opera.data_achizitie%TYPE) RETURN NUMBER AS alfa NUMBER; BEGIN SELECT COUNT(ROWID) INTO alfa FROM opera WHERE material = 'panza' AND data_achizitie = v_a;

304
RETURN alfa; END numar_opere; /

PROGRAMARE AVANSAT N ORACLE9i

Dac apare o eroare de compilare, utilizatorul o va corecta n fiierul editat i apoi va trimite compilatorului (cu opiunea OR REPLACE) fiierul modificat. Sintaxa pentru apelul unei funcii este: [ [schema.]nume_pachet.]nume_funcie [@dblink] [(lista_parametri_actuali) ]; O funcie stocat poate fi apelat n mai multe moduri. n continuare sunt prezentate trei exemple de apelare.
1) Apelarea funciei i atribuirea valorii acesteia ntr-o variabil de

legtur SQL*Plus:
VARIABLE val NUMBER EXECUTE :val := numar_opere(SYSDATE) PRINT val

Cnd este utilizat declaraia VARIABLE pentru variabilele host de tip NUMBER nu trebuie specificat dimensiunea, iar pentru cele de tip CHAR sau VARCHAR2 valoarea implicit este 1 sau poate fi specificat o alt valoare ntre paranteze. PRINT i VARIABLE sunt comenzi SQL*Plus.
2) Apelarea funciei ntr-o instruciune SQL: SELECT numar_opere(SYSDATE) FROM dual; 3) Apariia numelui funciei ntr-o comand din interiorul unui bloc

PL/SQL (de exemplu, ntr-o instruciune de atribuire):


SET SERVEROUTPUT ON ACCEPT data PROMPT 'dati data achizitionare' DECLARE num NUMBER; v_data opera.data_achizitie%TYPE := '&data'; BEGIN num := numar_opere(v_data); DBMS_OUTPUT.PUT_LINE('numarul operelor de arta achizitionate la data ' || TO_CHAR(v_data) || ' este ' || TO_CHAR(num)); END; / SET SERVEROUTPUT OFF

Exemplu: S se creeze o procedur stocat care pentru un anumit tip de oper de art (dat ca parametru) calculeaz numrul operelor de tipul respectiv din muzeu, numrul de specialiti care au expertizat sau au restaurat aceste opere, numrul de expoziii n care au fost expuse, precum i valoarea nominal total a acestora.

Programare n PL/SQL CREATE OR REPLACE PROCEDURE date_tip_opera (v_tip opera.tip%TYPE) AS FUNCTION nr_opere (v_tip opera.tip%TYPE) RETURN NUMBER IS v_numar NUMBER(3); BEGIN SELECT COUNT(*) INTO v_numar FROM opera WHERE tip = v_tip; RETURN v_numar; END nr_opere; FUNCTION valoare_totala (v_tip opera.tip%TYPE) RETURN NUMBER IS v_numar opera.valoare%TYPE; BEGIN SELECT SUM(valoare) INTO v_numar FROM opera WHERE tip = v_tip; RETURN v_numar; END valoare_totala; FUNCTION nr_specialisti (v_tip opera.tip%TYPE) RETURN NUMBER IS v_numar NUMBER(3); BEGIN SELECT COUNT(DISTINCT studiaza.cod_specialist) INTO v_numar FROM studiaza, opera WHERE studiaza.cod_opera = opera.cod_opera AND opera.tip = v_tip; RETURN v_numar; END nr_specialisti; FUNCTION nr_expozitii (v_tip opera.tip%TYPE) RETURN NUMBER IS v_numar NUMBER(3); BEGIN SELECT COUNT(DISTINCT figureaza_in.cod_expozitie) INTO v_numar FROM figureaza_in, opera WHERE figureaza_in.cod_opera = opera.cod_opera AND opera.tip = v_tip; RETURN v_numar; END nr_expozitii; BEGIN DBMS_OUTPUT.PUT_LINE('Numarul operelor de arta este '|| nr_opere(v_tip)); DBMS_OUTPUT.PUT_LINE('Valoarea operelor de arta este '|| valoare_totala(v_tip));

305

306

PROGRAMARE AVANSAT N ORACLE9i

DBMS_OUTPUT.PUT_LINE('Numarul de specialisti este '|| nr_specialisti(v_tip)); DBMS_OUTPUT.PUT_LINE('Numarul de expozitii este '|| nr_expozitii(v_tip); END date_tip_opera;

Instruciunea CALL
O instruciune specific pentru Oracle9i este CALL, care permite apelarea subprogramelor PL/SQL stocate (independente sau incluse n pachete) i a metodelor Java. CALL este o comand SQL care nu poate s apar de sine stttoare ntr-un bloc PL/SQL. Ea poate fi utilizat n PL/SQL doar dinamic, prin intermediul comenzii EXECUTE IMMEDIATE. Pentru executarea acestei comenzi, utilizatorul trebuie s aib privilegiul EXECUTE asupra subprogramului. Instruciunea poate fi executat interactiv din SQL*Plus. Comanda CALL are sintaxa urmtoare: CALL [schema.] [ {nume_tip_obiect | nume_pachet}. ] nume_subprogram [ (lista_parametri actuali) ] [@dblink_nume] [INTO :variabila_host] Identificatorul nume_subprogram este numele unui subprogram sau al unei metode apelate. Clauza INTO este folosit numai pentru variabilele de ieire ale unei funcii. Dac lipsete clauza @dblink_nume, atunci apelul se refer la baza de date local, iar ntr-un sistem distribuit clauza specific numele bazei de date ce conine subprogramul. Exemplu: Sunt prezentate dou exemple prin care o funcie PL/SQL este apelat din SQL*Plus, respectiv o procedur extern C este apelat, folosind SQL dinamic, dintr-un bloc PL/SQL.
CREATE OR REPLACE FUNCTION apelfunctie(a IN VARCHAR2) RETURN VARCHAR2 AS BEGIN DBMS_OUTPUT.PUT_LINE ('Apel functie cu ' || a); RETURN a; END apelfunctie; / SQL> SQL> SQL> Apel Call --apel valid VARIABLE v_iesire VARCHAR2(20) CALL apelfunctie('Salut!') INTO :v_iesire functie cu Salut! completed

SQL> PRINT v_iesire v_iesire Salut! DECLARE

Programare n PL/SQL

307

a NUMBER(7); x VARCHAR2(10); BEGIN EXECUTE IMMEDIATE 'CALL alfa_extern_procedura (:aa, :xx)' USING a, x; END; /

Modificarea i suprimarea subprogramelor PL/SQL


Pentru a lua n considerare modificarea unei proceduri sau funcii, recompilarea acesteia se face prin comanda: ALTER {FUNCTION | PROCEDURE} [schema.]nume COMPILE; Ca i n cazul tabelelor, funciile i procedurile pot fi suprimate cu ajutorul comenzii DROP. Aceasta presupune eliminarea subprogramelor din dicionarul datelor. DROP este o comand ce aparine limbajului de definire a datelor, astfel c se execut un COMMIT implicit att nainte, ct i dup comand. Atunci cnd este ters un subprogram prin comanda DROP, automat sunt revocate toate privilegiile acordate referitor la acest subprogram. Dac este utilizat sintaxa CREATE OR REPLACE, privilegiile acordate acestui subprogram rmn aceleai. Comanda DROP are urmtoarea sintax: DROP {FUNCTION | PROCEDURE} [schema.]nume;

Transferarea valorilor prin parametri


Lista parametrilor unui subprogram este compus din parametri de intrare (IN), de ieire (OUT) sau de intrare/ieire (IN OUT), separai prin virgul. Dac nu este specificat tipul parametrului, atunci implicit acesta este considerat de intrare (IN). Un parametru formal cu opiunea IN poate primi valori implicite chiar n cadrul comenzii de declarare. Acest parametru este read-only i deci, nu poate fi schimbat n corpul subprogramului. El acioneaz ca o constant. Parametrul actual corespunztor poate fi literal, expresie, constant sau variabil iniializat. Un parametru formal cu opiunea OUT este neiniializat i prin urmare, are automat valoarea null. n interiorul subprogramului, parametrilor cu opiunea OUT sau IN OUT trebuie s li se asigneze o valoare explicit. Dac nu se atribuie nici o valoare, atunci parametrul actual corespunztor va avea valoarea null. Parametrul actual trebuie s fie o variabil, nu poate fi o constant sau o expresie. Dac n timpul execuiei procedurii apare o excepie, atunci valorile parametrilor formali cu opiunile IN OUT sau OUT nu sunt copiate n valorile parametrilor actuali. Implicit, transmiterea parametrilor se face prin referin n cazul parametrilor IN i prin valoare n cazul parametrilor OUT sau IN OUT. Dac pentru

308

PROGRAMARE AVANSAT N ORACLE9i

realizarea unor performane se dorete transmiterea prin referin i a parametrilor IN OUT sau OUT, atunci se poate utiliza opiunea NOCOPY. Dac opiunea NOCOPY este asociat unui parametru IN, atunci se va genera o eroare la compilare, deoarece aceti parametri se transmit de fiecare dat prin referin. Opiunea NOCOPY va fi ignorat, iar parametrul va fi transmis prin valoare dac: parametrul actual este o component a unui tablou indexat (restricia nu se aplic dac parametrul este ntreg tabelul); parametrul actual este constrns prin specificarea unei precizii, a unei mrimi sau prin opiunea NOT NULL; parametrul formal i cel actual asociat sunt nregistrri care fie au fost declarate implicit ca variabile contor ntr-un ciclu LOOP, fie au fost declarate explicit prin %ROWTYPE, dar constrngerile pe cmpurile corespunztoare difer; transmiterea parametrului actual cere o conversie implicit a tipului; subprogramul este o parte a unui apel de tip RPC (remote procedure call), iar n acest caz, parametrii fiind transmii n reea, transferul nu se poate face prin referin.

Atunci cnd este apelat o procedur PL/SQL, sistemul Oracle furnizeaz dou metode pentru definirea parametrilor actuali: specificarea explicit prin nume i specificarea prin poziie. Exemplu: Sunt prezentate diferite moduri pentru apelarea procedurii p1.
CREATE PROCEDURE p1(a IN NUMBER, b IN VARCHAR2, c IN DATE, d OUT NUMBER) AS ; DECLARE var_a NUMBER; var_b VARCHAR2; var_c DATE; var_d NUMBER; BEGIN --specificare prin poziie p1(var_a,var_b,var_c,var_d); --specificare prin nume p1(b=>var_b,c=>var_c,d=>var_d,a=>var_a); --specificare prin nume i poziie p1(var_a,var_b,d=>var_d,c=>var_c); END;

Dac este utilizat specificaia prin poziie, parametrii care au primit o valoare implicit trebuie s fie plasai la sfritul listei parametrilor actuali. Exemplu: Fie proces_data o procedur care proceseaz n mod normal data zilei curente, dar care opional poate procesa i alte date. Dac nu se specific

Programare n PL/SQL

309

parametrul actual corespunztor parametrului formal plan_data, atunci acesta va lua automat valoarea dat implicit (data curent a sistemului).
PROCEDURE proces_data(data_in plan_data IN NUMBER, IN DATE := SYSDATE) IS

Urmtoarele comenzi reprezint apeluri corecte ale procedurii proces_data:


proces_data(10); proces_data(10,SYSDATE+1); proces_data(plan_data=>SYSDATE+1,data_in=>10);

O declaraie de subprogram (procedur sau funcie) fr parametri este specificat fr paranteze. De exemplu, dac procedura react_calc_dur i funcia obt_date nu au parametri, atunci:
react_calc_dur; react_calc_dur(); data_mea := obt_date; -- apel corect -- apel incorect -- apel corect

Module overload
Dou sau mai multe module pot s aib aceleai nume, dar s difere prin lista parametrilor. Aceste module sunt numite module overload (suprancrcate). Funcia TO_CHAR este un exemplu de modul overload. Exist o singur funcie, TO_CHAR, pentru a converti date numerice i calendaristice n date de tip caracter. n cazul unui apel, compilatorul compar parametrii actuali cu listele parametrilor formali pentru modulele overload i execut modulul corespunztor. Toate programele overload trebuie s fie definite n acelai bloc PL/SQL (bloc anonim, modul sau pachet). Modulele overload pot s apar n programele PL/SQL fie n seciunea declarativ a unui bloc, fie n interiorul unui pachet. Suprancrcarea subprogramelor nu se poate face pentru funcii sau proceduri stocate, dar este permis pentru subprograme locale, subprograme care apar n pachete sau pentru metode. Observaii:

Dou subprograme overload trebuie s difere, cel puin prin tipul unuia dintre parametri. Dou subprograme nu pot fi overload dac parametrii lor formali difer numai prin tipurile lor i dac acestea sunt nite subtipuri care se bazeaz pe acelai tip de date. Nu este suficient ca lista parametrilor subprogramelor overload s difere numai prin numele parametrilor formali. Nu este suficient ca lista parametrilor subprogramelor overload s difere numai prin tipul acestora (IN, OUT, IN OUT). PL/SQL nu poate face diferena (la apelare) ntre tipurile IN i OUT. Nu este suficient ca funciile overload s difere doar prin tipul de date returnat (tipul de date specificat n clauza RETURN a funciei).

Exemplu: Urmtoarele subprograme nu pot fi overload.

310

PROGRAMARE AVANSAT N ORACLE9i 1) FUNCTION alfa(par IN POSITIVE); FUNCTION alfa(par IN BINARY_INTEGER); 2) FUNCTION alfa(par IN NUMBER); FUNCTION alfa(parar IN NUMBER); 3) PROCEDURE beta(par IN VARCHAR2) IS; PROCEDURE beta(par OUT VARCHAR2) IS;

Exemplu: S se creeze dou funcii (locale) cu acelai nume care s calculeze media valorilor operelor de art de un anumit tip. Prima funcie va avea un argument reprezentnd tipul operelor de art, iar cea de-a doua va avea dou argumente, unul reprezentnd tipul operelor de art, iar cellalt reprezentnd stilul operelor pentru care se calculeaz valoarea medie (funcia va calcula media valorilor operelor de art de un anumit tip i care aparin unui stil specificat).
DECLARE medie1 NUMBER(10,2); medie2 NUMBER(10,2); FUNCTION valoare_medie (v_tip opera.tip%TYPE) RETURN NUMBER IS medie NUMBER(10,2); BEGIN SELECT AVG(valoare) INTO medie FROM opera WHERE tip = v_tip; RETURN medie; END; FUNCTION valoare_medie (v_tip opera.tip%TYPE, v_stil opera.stil%TYPE) RETURN NUMBER IS medie NUMBER(10,2); BEGIN SELECT AVG(valoare) INTO medie FROM opera WHERE tip = v_tip AND stil = v_stil; RETURN medie; END; BEGIN medie1 := valoare_medie('pictura'); DBMS_OUTPUT.PUT_LINE(Media valorilor picturilor din muzeu este || medie1); medie2 := valoare_medie(pictura , impresionism ); DBMS_OUTPUT.PUT_LINE(Media valorilor picturilor impresioniste din muzeu este || medie2); END;

Programare n PL/SQL

311

Procedur versus funcie


Dup cum am mai subliniat, n general, procedura este utilizat pentru realizarea unor aciuni, iar funcia este folosit pentru calculul unei valori. Pot fi marcate cteva deosebiri eseniale ntre funcii i proceduri. Procedura se execut ca o comand PL/SQL, iar funcia se invoc n cadrul unei expresii. Procedura poate returna (sau nu) una sau mai multe valori, iar funcia trebuie s returneze o singur valoare. Procedura nu trebuie s conin clauza RETURN expresie, iar funcia trebuie s conin aceast opiune. De asemenea, pot fi remarcate cteva elemente eseniale, comune att funciilor ct i procedurilor. Ambele pot: accepta valori implicite; conine seciuni declarative, executabile i de tratare a erorilor; utiliza specificarea prin nume sau poziie a parametrilor; accepta parametri NOCOPY.

Tratarea excepiilor
Dac apare o eroare ntr-un subprogram, atunci este declanat o excepie. Dac excepia este tratat n subprogram, atunci blocul se termin i controlul trece la seciunea de tratare a erorilor din subprogramul respectiv. Dac nu exist o tratare a excepiei n subprogram, atunci controlul trece la programul apelant de nivel imediat superior (n partea de tratare a erorilor), respectnd regulile de propagare a excepiilor. n acest caz, valorile parametrilor de tip OUT sau IN OUT nu sunt returnate parametrilor actuali. Acetia vor avea aceleai valori, ca i cum subprogramul nu ar fi fost apelat.

Recursivitate
Recursivitatea este o tehnic important pentru simplificarea modelrii algoritmilor. Un subprogram recursiv presupune c acesta se apeleaz pe el nsui. n Oracle, o problem delicat este legat de locul unde se plaseaz un apel recursiv. De exemplu, dac apelul este n interiorul unei comenzi FOR specifice cursoarelor sau ntre comenzile OPEN i CLOSE, atunci la fiecare apel este deschis alt cursor. n felul acesta, programul poate depi limita admis de cursoare deschise la un moment dat (OPEN_CURSORS), setat n parametrul de iniializare Oracle. Exemplu: S se calculeze recursiv al m-lea termen din irul lui Fibonacci.
CREATE OR REPLACE FUNCTION fibo(m POSITIVE) RETURN INTEGER AS BEGIN IF (m = 1) OR (m = 2) THEN RETURN 1; ELSE RETURN fibo(m-1) + fibo(m-2);

312
END IF; END fibona;

PROGRAMARE AVANSAT N ORACLE9i

Exemplu: S se calculeze iterativ al m-lea termen din irul lui Fibonacci.


CREATE OR REPLACE FUNCTION fibo(m POSITIVE) RETURN INTEGER AS ter1 INTEGER := 1; ter2 INTEGER := 0; valoare INTEGER; BEGIN IF (m = 1) OR (m = 2) THEN RETURN 1; ELSE valoare := ter1 + ter2; FOR i IN 3..m LOOP ter2 := ter1; ter1 := valoare; valoare := ter1 + ter2; END LOOP; RETURN valoare; END IF; END fibo;

Declaraii forward
Subprogramele se numesc reciproc recursive dac se apeleaz unul pe altul, n mod direct sau indirect. Declaraiile forward permit definirea subprogramelor reciproc recursive. n PL/SQL, un identificator trebuie declarat nainte de a fi folosit. De asemenea, un subprogram trebuie declarat nainte de a fi apelat. Exemplu:
PROCEDURE alfa ( ... ) IS BEGIN beta( ... ); -- apel incorect END; PROCEDURE beta ( ... ) IS BEGIN END;

n acest exemplu, procedura beta nu poate fi apelat deoarece nu este nc declarat. Problema se poate rezolva simplu n acest caz, inversnd ordinea celor dou proceduri. Aceast soluie nu este eficient ntotdeauna (de exemplu, dac i procedura beta conine un apel al procedurii alfa). PL/SQL permite un tip special, numit forward, de declarare a unui subprogram. El const dintr-o specificare a antetului unui subprogram, terminat prin caracterul ;. O declaraie de tip forward pentru procedura beta are forma:

Programare n PL/SQL PROCEDURE beta ( ... ); -- declaraie forward PROCEDURE alfa ( ... ) IS BEGIN beta( ... ); END; PROCEDURE beta ( ... ) IS BEGIN END;

313

Declaraiile forward pot fi folosite pentru a defini subprograme ntr-o anumit ordine logic, pentru a defini subprograme reciproc recursive sau pentru a grupa subprograme ntr-un pachet. Lista parametrilor formali din declaraia forward trebuie s fie identic cu cea corespunztoare corpului subprogramului. Corpul subprogramului poate aprea oriunde dup declaraia sa forward, dar s rmn n aceeai unitate de program.

Utilizarea n expresii SQL a funciilor definite de utilizator


O funcie stocat poate fi referit ntr-o comand SQL la fel ca orice funcie standard furnizat de sistem (built-in function), dar cu anumite restricii. Funciile PL/SQL definite de utilizator pot fi apelate din orice expresie SQL n care se pot folosi funcii SQL standard. Funciile PL/SQL pot s apar n: lista de cmpuri a comenzii SELECT; condiia clauzelor WHERE i HAVING; clauzele CONNECT BY, START WITH, ORDER BY i GROUP BY; clauza VALUES a comenzii INSERT; clauza SET a comenzii UPDATE. Exemplu: S se afieze operele de art (titlu, valoare, stare) a cror valoare este mai mare dect valoarea medie a tuturor operelor de art din muzeu.
CREATE OR REPLACE FUNCTION valoare_medie RETURN NUMBER AS v_val_mediu opera.valoare%TYPE; BEGIN SELECT AVG(valoare) INTO v_val_mediu FROM opera; RETURN v_val_mediu; END;

Referirea acestei funcii ntr-o comand SQL se poate face prin secvena:
SELECT titlu, valoare, stare FROM opera WHERE valoare >= valoare_medie;

314

PROGRAMARE AVANSAT N ORACLE9i

Exist restricii referitoare la folosirea funciilor definite de utilizator ntr-o comand SQL. Cteva dintre acestea, care s-au pstrat i pentru Oracle9i, vor fi enumerate n continuare: funcia definit de utilizator trebuie s fie o funcie stocat (procedurile stocate nu pot fi apelate n expresii SQL), nu poate fi local altui bloc; funcia definit de utilizator trebuie s fie o funcie linie i nu una grup (restricia dispare n Oracle9i); funcia apelat dintr-o comand SELECT sau din comenzi paralelizate INSERT, UPDATE i DELETE nu poate modifica tabelele bazei de date; funcia apelat dintr-o comand UPDATE sau DELETE nu poate interoga sau modifica tabele ale bazei reactualizate chiar de aceste comenzi (table mutating); funcia apelat din comenzile SELECT, INSERT, UPDATE sau DELETE nu poate conine comenzi LCD (COMMIT), ALTER SYSTEM, SET ROLE sau comenzi LDD (CREATE); funcia nu poate aprea n clauza CHECK a unei comenzi CREATE/ALTER TABLE; funcia nu poate fi folosit pentru a specifica o valoare implicit pentru o coloan n cadrul unei comenzi CREATE/ALTER TABLE; funcia poate fi utilizat ntr-o comand SQL numai de ctre proprietarul funciei sau de utilizatorul care are privilegiul EXECUTE asupra acesteia; parametrii unei funcii PL/SQL apelate dintr-o comand SQL trebuie s fie specificai prin poziie (specificarea prin nume nefiind permis); funcia definit de utilizator, apelabil dintr-o comand SQL, trebuie s aib doar parametri de tip IN, cei de tip OUT i IN OUT nefiind acceptai; parametrii formali ai unui subprogram funcie trebuie s fie de tip specific bazei de date (NUMBER, CHAR, VARCHAR2, ROWID, LONG, LONGROW, DATE etc.) i nu de tipuri PL/SQL (BOOLEAN, RECORD etc.); tipul returnat de un subprogram funcie trebuie s fie un tip intern pentru server, nu un tip PL/SQL; funcia nu poate apela un subprogram care nu respect restriciile anterioare. Exemplu:
CREATE OR REPLACE FUNCTION calcul (p_val NUMBER) RETURN NUMBER IS BEGIN INSERT INTO opera(cod_opera, tip, data_achizitie, valoare) VALUES (1358, 'gravura', SYSDATE, 700000); RETURN (p_val*7); END; /

Programare n PL/SQL UPDATE SET WHERE opera valoare = calcul (550000) cod_opera = 7531;

315

Comanda UPDATE va returna o eroare deoarece tabelul opera este mutating. Operaia de reactualizare este ns permis asupra oricrui alt tabel diferit de opera.

Informaii referitoare la subprograme


Informaiile referitoare la subprogramele PL/SQL i modul de acces la aceste informaii sunt urmtoarele: codul surs, utiliznd vizualizarea USER_SOURCE din dicionarul datelor; informaii generale, utiliznd vizualizarea USER_OBJECTS din dicionarul datelor; tipul parametrilor (IN, OUT, IN OUT), utiliznd comanda DESCRIBE din SQL*Plus; p-code (nu este accesibil utilizatorilor); erorile la compilare, utiliznd vizualizarea USER_ERRORS din dicionarul datelor sau comanda SHOW ERRORS; informaii de depanare, utiliznd pachetul DBMS_OUTPUT. Atunci cnd este creat un subprogram stocat, informaiile referitoare la subprogram sunt depuse n dicionarul datelor. Vizualizarea USER_OBJECTS conine informaii generale despre toate obiectele prelucrate n baza de date i, n particular, despre subprogramele stocate. Vizualizarea USER_OBJECTS are urmtoarele cmpuri: OBJECT_NAME numele obiectului; OBJECT_TYPE tipul obiectului (PROCEDURE, FUNCTION etc.); OBJECT_ID identificator intern al obiectului; CREATED data la care a fost creat obiectul; LAST_DDL_TIME data ultimei modificri a obiectului; TIMESTAMP data i momentul ultimei recompilri; STATUS starea de validitate a obiectului. Pentru a verifica dac recompilarea explicit (ALTER) sau implicit a avut succes se poate verifica starea subprogramelor utiliznd coloana STATUS din vizualizarea USER_OBJECTS. Orice obiect are o stare (status) sesizat n dicionarul datelor, care poate fi: VALID (obiectul a fost compilat i poate fi folosit atunci cnd este referit); INVALID (obiectul trebuie compilat nainte de a fi folosit). Exemplu: S se listeze n ordine alfabetic, procedurile i funciile deinute de utilizatorul curent, precum i starea acestora.

316
SELECT FROM WHERE ORDER BY

PROGRAMARE AVANSAT N ORACLE9i OBJECT_NAME, OBJECT_TYPE, STATUS USER_OBJECTS OBJECT_TYPE IN ('PROCEDURE','FUNCTION') OBJECT_NAME;

Dup ce subprogramul a fost creat, codul surs al acestuia poate fi obinut consultnd vizualizarea USER_SOURCE din dicionarul datelor. Vizualizarea are urmtoarele cmpuri: NAME (numele obiectului), TYPE (tipul obiectului), LINE (numrul liniei din codul surs), TEXT (textul liniilor codului surs). Exemplu: S se afieze codul complet pentru funcia numar_opere.
SELECT FROM WHERE ORDER BY TEXT USER_SOURCE NAME = 'NUMAR_OPERE' LINE;

Exemplu: S se scrie o procedur care recompileaz toate obiectele invalide din schema personal.
CREATE OR REPLACE PROCEDURE recompileaza IS CURSOR obj_curs IS SELECT OBJECT_TYPE, OBJECT_NAME FROM USER_OBJECTS WHERE STATUS = 'INVALID' AND OBJECT_TYPE IN ('PROCEDURE', 'FUNCTION', PACKAGE', 'PACKAGE BODY', 'VIEW'); BEGIN FOR obj_rec IN obj_curs LOOP DBMS_DDL.ALTER_COMPILE(obj_rec.OBJECT_TYPE, USER, obj_rec.OBJECT_NAME); END LOOP; END recompileaza;

Atunci cnd se recompileaz un obiect PL/SQL, server-ul va recompila orice obiect invalid de care depinde acesta. Dac la recompilarea automat implicit a procedurilor locale dependente apar probleme, atunci starea obiectului va rmne INVALID i server-ul Oracle va semnala o eroare. Prin urmare: este preferabil ca recompilarea s fie manual, explicit utiliznd comanda ALTER (PROCEDURE, FUNCTION, TRIGGER, PACKAGE) cu opiunea COMPILE; este necesar ca dup o schimbare referitoare la obiectele bazei, recompilarea s se fac ct mai repede. Vizualizarea USER_ERRORS afieaz textul tuturor erorilor de compilare. Cmpurile acesteia (NAME, TYPE, SEQUENCE, LINE, POSITION, TEXT) sunt analizate n capitolul referitor la tratarea excepiilor.

Programare n PL/SQL

317

Pentru a obine valori (de exemplu, valoarea contorului pentru un LOOP, valoarea unei variabile nainte i dup o atribuire etc.) i mesaje (de exemplu, prsirea unui subprogram, apariia unei operaii etc.) dintr-un bloc PL/SQL pot fi utilizate procedurile pachetului DBMS_OUTPUT. Aceste informaii se cumuleaz ntr-un buffer care poate fi consultat ulterior.

Dependena subprogramelor
Atunci cnd este compilat un subprogram, toate obiectele Oracle care sunt referite vor fi nregistrate n dicionarul datelor. Subprogramul este dependent de aceste obiecte. Un subprogram care are erori la compilare este marcat ca INVALID n dicionarul datelor. Un subprogram stocat poate deveni, de asemenea, invalid dup execuia unei operaii LDD asupra unui obiect de care depinde. Obiecte dependente View Procedure Function Package Specification Package Body Database Trigger Obiecte referite Table View Procedure Function Synonym Package Specification

Modificarea definiiei unui obiect referit poate s influeneze (sau nu) funcionarea normal a obiectului dependent. Exist dou tipuri de dependene: dependen direct, n care obiectul dependent (procedure sau function) face referin direct la un obiect de tip table, view, sequence, procedure, function; dependen indirect, n care obiectul dependent (procedure sau function) face referin indirect la un obiect de tip table, view, sequence, procedure, function prin intermediul unui view, procedure sau function. n cazul dependenelor locale, atunci cnd un obiect referit este modificat, obiectele dependente sunt invalidate. La urmtorul apel al obiectului invalidat, acesta va fi recompilat automat de ctre server-ul Oracle. n cazul dependenelor la distan, procedurile stocate local i toate obiectele dependente vor fi invalidate. Ele nu vor fi recompilate automat la urmtorul apel. Exemplu: Se presupune c procedura filtru va referi direct tabelul opera i c procedura adaug va reactualiza tabelul opera prin intermediul unei vizualizri nou_opera. Pentru aflarea dependenelor directe se poate utiliza vizualizarea USER_DEPENDENCIES din dicionarul datelor.
SELECT NAME, TYPE, REFENCED_NAME, REFENCED_TYPE FROM USER_DEPENDENCIES WHERE REFENCED_NAME IN ('opera', 'nou_opera'); NAME TYPE REFENCED_NAME REFENCED_TYPE

318
filtru adaug nou_opera Procedure Procedure View

PROGRAMARE AVANSAT N ORACLE9i opera nou_opera opera Table View Table

Dependenele indirecte pot fi afiate utiliznd vizualizrile DEPTREE i IDEPTREE. Vizualizarea DEPTREE afieaz o reprezentare a tuturor obiectelor dependente (direct sau indirect). Vizualizarea IDEPTREE afieaz o reprezentare a aceleai informaii, sub forma unui arbore. Pentru a utiliza aceste vizualizri furnizate de sistemul Oracle trebuie: executat scriptul UTLDTREE; executat procedura DEPTREE_FILL (are trei argumente: tipul obiectului referit, schema obiectului referit, numele obiectului referit). Exemplu:
@UTLDTREE EXECUTE DEPTREE_FILL ('TABLE', 'SCOTT', 'opera') SELECT NESTED_LEVEL, TYPE, NAME FROM DEPTREE ORDER BY SEQ#; NESTED_LEVEL 0 1 2 1 SELECT FROM * IDEPTREE; TYPE Table View Procedure Procedre NAME opera nou_opera adaug filtru

DEPENDENCIES TABLE nume_schema.opera VIEW nume_schema.nou_opera PROCEDURE nume_schema.adaug PROCEDURE nume_schema.filtru

Dependenele la distan sunt tratate printr-una dintre modalitile alese de utilizator, modelul timestamp (implicit) sau modelul signature. Fiecare unitate PL/SQL are un timestamp (etichet de timp) care este setat atunci cnd unitatea este creat sau recompilat i care este depus n cmpul LAST_DDL_TIME din dicionarul datelor. Modelul timestamp realizeaz compararea momentelor ultimei modificri a celor dou obiecte analizate. Dac obiectul bazei are momentul ultimei modificri mai recent dect cel al obiectului dependent, atunci obiectul dependent va fi recompilat. Modelul signature (semntur) determin momentul la care obiectele bazei distante trebuie recompilate. Cnd este creat un subprogram, o signature este depus n dicionarul datelor, alturi de p-code. Aceasta conine: numele construciei PL/SQL (PROCEDURE, FUNCTION, PACKAGE), tipurile parametrilor, ordinea parametrilor, numrul acestora i modul de transmitere (IN, OUT, IN OUT). Dac

Programare n PL/SQL

319

parametrii se schimb, atunci evident signature se schimb. Pentru a folosi modelul signature este necesar setarea parametrului REMOTE_DEPENDENCIES_MODE la SIGNATURE. Aceasta se poate realiza prin: 1) comanda ALTER SESSION, care va afecta doar sesiunea curent: ALTER SESSION SET REMOTE_DEPENDENCIES_MODE = SIGNATURE;
2) comanda ALTER SYSTEM, care va afecta ntreaga baz de date

(toate sesiunile), dar trebuie avut privilegiul sistem pentru a utiliza aceast instruciune: ALTER SYSTEM SET REMOTE_DEPENDENCIES_MODE = SIGNATURE; 3) adugarea n fiierul de iniializare a unei linii de forma: REMOTE_DEPENDENCIES_MODE = SIGNATURE;

Recompilarea procedurilor i a funciilor dependente este fr succes dac: obiectul referit este suprimat (DROP) sau redenumit (RENAME); tipul coloanei referite este schimbat; o vizualizare referit este nlocuit printr-o vizualizare ce conine alte coloane; lista parametrilor unei proceduri referite este modificat. Recompilarea procedurilor i funciilor dependente este cu succes dac: tabelul referit are noi coloane; corpul PL/SQL al unei proceduri referite a fost modificat i recompilat cu succes. Erorile datorate dependenelor pot fi minimizate: utiliznd comenzi SELECT cu opiunea *; incluznd lista coloanelor n comanda INSERT; declarnd variabile cu atributul %TYPE; declarnd nregistrri cu atributul %ROWTYPE; Concluzii: Dac procedura depinde de un obiect local, atunci se face recompilare automat la prima reexecuie. Dac procedura depinde de o procedur distant, atunci se face recompilare automat, dar la a doua reexecuie. Este preferabil o recompilare manual pentru prima reexecuie sau implementarea unei strategii de reinvocare a ei (a doua oar). Dac procedura depinde de un obiect distant, dar care nu este procedur, atunci nu se face recompilare automat.

Rutine externe
PL/SQL a fost special conceput pentru Oracle i este specializat pentru procesarea tranzaciilor SQL. Totui, ntr-o aplicaie complex pot s apar cerine i funcionaliti care sunt mai eficient de implementat n C, Java sau alt limbaj de

320

PROGRAMARE AVANSAT N ORACLE9i

programare. De exemplu, Java este un limbaj portabil cu un model de securitate bine definit, care lucreaz excelent pentru aplicaii Internet. Dac aplicaia trebuie s efectueze anumite aciuni care nu pot fi implementate optim utiliznd PL/SQL, atunci este preferabil s fie utilizate alte limbaje care realizeaz performant aciunile respective. n acest caz este necesar comunicarea ntre diferite module ale aplicaiei care sunt scrise n limbaje diferite. Pn la versiunea Oracle8, modalitatea de comunicare ntre PL/SQL i alte limbaje (de exemplu, limbajul C) a fost utilizarea pachetelor DBMS_PIPE i/sau DBMS_ALERT. ncepnd cu Oracle8, comunicarea este simplificat prin utilizarea rutinelor externe. O rutin extern este o procedur sau o funcie scris ntr-un limbaj diferit de PL/SQL, dar apelabil dintr-un program scris n PL/SQL. PL/SQL extinde funcionalitatea server-ului Oracle, furniznd o interfa pentru apelarea rutinelor externe. Orice bloc PL/SQL executat pe server sau pe client poate apela o rutin extern. Singurul limbaj acceptat pentru rutine externe n Oracle8 este limbajul C. Pentru a marca apelarea unei rutine externe n programul PL/SQL este definit un punct de intrare (wrapper) care direcioneaz spre codul extern (program PL/SQL wrapper cod extern). Pentru crearea unui wrapper este utilizat o clauz special (AS EXTERNAL) n cadrul comenzii CREATE OR REPLACE PROCEDURE. De fapt, clauza conine informaii referitoare la numele bibliotecii n care se gsete subprogramul extern (clauza LIBRARY), numele rutinei externe (clauza NAME) i corespondena (C <> PL/SQL) dintre tipurile de date (clauza PARAMETERS). n ultimele versiuni s-a renunat la clauza AS EXTERNAL. Rutinele externe (scrise n C) sunt compilate, apoi depuse ntr-o bibliotec dinamic (DLL dynamic link library) i sunt ncrcate doar atunci cnd este necesar. Dac se invoc o rutin extern scris n C, trebuie setat conexiunea spre aceast rutin. Un proces numit extproc este declanat automat de ctre server. La rndul su, procesul extproc va ncrca biblioteca identificat prin clauza LIBRARY i va apela rutina respectiv. Oracle8i permite utilizarea de rutine externe scrise n Java. De asemenea, un wrapper poate include specificaii de apelare, prin utilizarea clauzei LANGUAGE. De fapt, aceste specificaii permit apelarea rutinelor externe scrise n orice limbaj. De exemplu, o procedur scris ntr-un limbaj diferit de C sau Java poate fi utilizat n SQL sau PL/SQL dac procedura respectiv este apelabil din C. n felul acesta, biblioteci standard scrise n alte limbaje de programare pot fi apelate din programe PL/SQL. Procedura PL/SQL executat pe server-ul Oracle poate apela o rutin extern scris n limbajul C, depus ntr-o bibliotec partajat. Procedura C se execut ntr-un spaiu adres diferit de cel al server-ului Oracle, n timp ce unitile PL/SQL i metodele Java se execut n spaiul adres al server-ului. JVM (Java Virtual Machine) de pe server va executa metoda Java n mod direct, fr a fi necesar procesul extproc. Maniera de ncrcare depinde de limbajul n care este scris rutina. Pentru a apela rutine externe C, server-ul trebuie s cunoasc poziionarea bibliotecii dinamice DLL. Acest lucru este furnizat de

Programare n PL/SQL

321

alias-ul bibliotecii din clauza AS LANGUAGE. Pentru apelarea unei rutine externe Java se va ncrca clasa Java n baza de date. Este necesar doar crearea unui wrapper care direcioneaz ctre codul extern. Spre deosebire de rutinele externe C, nu este necesar nici biblioteca i nici setarea conexiunii spre rutina extern.

Clauza LANGUAGE din cadrul comenzii de creare a unui subprogram, specific limbajul n care este scris rutina (procedur extern C sau metod Java) i are urmtoarea form: {IS | AS} LANGUAGE {C | JAVA} Pentru o procedur C sunt date informaii referitoare la numele acesteia (clauza NAME); alias-ul bibliotecii n care se gsete (clauza LIBRARY); opiuni referitoare la tipul, poziia, lungimea, modul de transmitere (prin valoare sau prin referin) al parametrilor (clauza PARAMETERS); posibilitatea ca rutina extern s acceseze informaii despre parametri, excepii, alocarea memoriei utilizator (clauza WITH CONTEXT). LIBRARY nume_biblioteca [NAME nume_proc_c] [WITH CONTEXT] [PARAMETERS (parametru_extern [, parametru_extern ] ) ] Pentru o metod Java, n clauz trebuie specificat doar signatura metodei (lista tipurilor parametrilor n ordinea apariiei). Exemplu:
CREATE OR REPLACE FUNCTION calc (x IN REAL) RETURN NUMBER AS LANGUAGE C LIBRARY biblioteca NAME "c_calc" PARAMETERS (x BY REFERENCES);

O rutin extern nu este apelat direct, ci se apeleaz subprogramul PL/SQL care refer rutina extern. Apelarea poate s apar n: blocuri anonime, subprograme independente sau care aparin unui pachet, metode ale unui tip obiect, declanatori baz de date, comenzi SQL care apeleaz funcii (n acest caz, trebuie utilizat clauza PRAGMA RESTRICT_REFERENCES). De remarcat c o metod Java poate fi apelat din orice bloc PL/SQL, subprogram sau pachet. JDBC (Java Database Connectivity), care reprezint interfaa Java standard pentru conectare la baze de date relaionale, i SQLJ permit apelarea de blocuri PL/SQL din programe Java. SQLJ face posibil incorporarea operaiilor SQL n codul Java. Standardul SQLJ acoper doar operaii SQL statice. Oracle9i SQLJ include extensii pentru a suporta direct SQL dinamic. O alt modalitate de a ncrca metode Java este folosirea interactiv n SQL*Plus a comenzii CREATE JAVA instruciune.

Funcii tabel

322

PROGRAMARE AVANSAT N ORACLE9i

O funcie tabel (table function) returneaz drept rezultat un set de linii (de obicei, sub forma unei colecii). Aceast funcie poate fi interogat direct printr-o comand SQL, ca i cum ar fi un tabel al bazei de date. n felul acesta, funcia poate fi utilizat n clauza FROM a unei cereri. O funcie tabel conduct (pipelined table function) este similar unei funcii tabel, dar returneaz datele iterativ, pe msur ce acestea sunt obinute, nu toate deodat. Aceste funcii sunt mai eficiente deoarece informaia este returnat imediat cum este obinut. Conceptul de funcie tabel conduct a fost introdus n versiunea Oracle9i. Utilizatorul poate s defineasc astfel de funcii. De asemenea, este posibil execuia paralel a funciilor tabel (evident i a celor clasice). n acest caz, funcia trebuie s conin n declaraie opiunea PARALLEL_ENABLE. Funcia tabel conduct accept orice argument pe care l poate accepta o funcie obinuit i trebuie s returneze o colecie (nested table sau varray). Ea este declarat specificnd cuvntul cheie PIPELINED n comanda CREATE OR REPLACE FUNCTION. Funcia tabel conduct trebuie s se termine printr-o comand RETURN simpl, care nu ntoarce nici o valoare. Pentru a returna un element individual al coleciei este folosit comanda PIPE ROW, care poate s apar numai n corpul unei funcii tabel conduct, n caz contrar generndu-se o eroare. Comanda poate fi omis dac funcia tabel conduct nu returneaz nici o linie. Dup ce funcia a fost creat, ea poate fi apelat dintr-o cerere SQL utiliznd operatorul TABLE. Cererile referitoare la astfel de funcii pot s includ cursoare i referine la cursoare, respectndu-se semantica de la cursoarele clasice. Funcia tabel conduct nu poate s apar n comenzile INSERT, UPDATE, DELETE. Totui, pentru a realiza o reactualizare, poate fi creat o vizualizare relativ la funcia tabel i folosit un declanator INSTEAD OF. Exemplu: S se obin o instan a unui tabel ce conine informaii referitoare la denumirea zilelor sptmnii. Problema este rezolvat n dou variante. Prima reprezint o soluie clasic, iar a doua variant implementeaz problema cu ajutorul unei funcii tabel conduct. Varianta 1:
CREATE TYPE t_linie AS OBJECT ( idl NUMBER, sir VARCHAR2(20)); CREATE TYPE t_tabel AS TABLE OF t_linie; CREATE OR REPLACE FUNCTION calc1 RETURN t_tabel AS v_tabel t_tabel; BEGIN v_tabel := t_tabel (t_linie (1, 'luni')); FOR j IN 2..7 LOOP v_tabel.EXTEND;

Programare n PL/SQL IF j = 2 THEN v_tabel(j) := t_linie (2, 'marti'); ELSIF j = 3 THEN v_tabel(j) := t_linie (3, 'miercuri'); ELSIF j = 4 THEN v_tabel(j) := t_linie (4, 'joi'); ELSIF j = 5 THEN v_tabel(j) := t_linie (5, 'vineri'); ELSIF j = 6 THEN v_tabel(j) := t_linie (6, 'sambata'); ELSIF j = 7 THEN v_tabel(j) := t_linie (7, 'duminica'); END IF; END LOOP; RETURN v_tabel; END calc1;

323

Funcia calc1 poate fi invocat n clauza FROM a unei comenzi SELECT:


SELECT * FROM TABLE (CAST (calc1 AS t_tabel));

Varianta 2:
CREATE OR REPLACE FUNCTION calc2 RETURN t_tabel PIPELINED AS v_linie t_linie; BEGIN FOR j IN 1..7 LOOP v_linie := CASE j WHEN 1 THEN t_linie (1, 'luni') WHEN 2 THEN t_linie (2, 'marti') WHEN 3 THEN t_linie (3, 'miercuri') WHEN 4 THEN t_linie (4, 'joi') WHEN 5 THEN t_linie (5, 'vineri') WHEN 6 THEN t_linie (6, 'sambata') WHEN 7 THEN t_linie (7, 'duminica') END; PIPE ROW (v_linie); END LOOP; RETURN; END calc2;

Se observ c tabelul este implicat doar n tipul rezultatului. Pentru apelarea funciei calc2 este folosit sintaxa urmtoare:
SELECT * FROM TABLE (calc2);

Funciile tabel sunt folosite frecvent pentru conversii de tipuri de date. Oracle9i introduce posibilitatea de a crea o funcie tabel care returneaz un tip

324

PROGRAMARE AVANSAT N ORACLE9i

PL/SQL (definit ntr-un bloc). Funcia tabel care furnizeaz (la nivel de pachet) drept rezultat un tip de date trebuie s fie de tip conduct. Pentru apelare este utilizat sintaxa simplificat (fr CAST). Exemplu:
CREATE OR REPLACE PACKAGE exemplu IS TYPE t_linie IS RECORD (idl NUMBER, sir VARCHAR2(20)); TYPE t_tabel IS TABLE OF t_linie; END exemplu; CREATE OR REPLACE FUNCTION calc3 RETURN exemplu.t_tabel PIPELINED AS v_linie exemplu.t_linie; BEGIN FOR j IN 1..7 LOOP CASE j WHEN 1 THEN v_linie.idl := 1; v_linie.sir := 'luni'; WHEN 2 THEN v_linie.idl := 2; v_linie.sir := 'marti'; WHEN 3 THEN v_linie.idl := 3; v_linie.sir := 'miercuri'; WHEN 4 THEN v_linie.idl := 4; v_linie.sir := 'joi'; WHEN 5 THEN v_linie.idl := 5; v_linie.sir := 'vineri'; WHEN 6 THEN v_linie.idl := 6; v_linie.sir := 'sambata'; WHEN 7 THEN v_linie.idl := 7; v_linie.sir := 'duminica'; END CASE; PIPE ROW (v_linie); END LOOP; RETURN; END calc3;

Procesarea tranzaciilor autonome


Tranzacia este o unitate logic de lucru, adic o secven de comenzi care trebuie s se execute ca un ntreg pentru a menine consistena bazei de date. n mod uzual, o tranzacie poate s cuprind mai multe blocuri, iar ntr-un bloc pot s fie mai multe tranzacii. O tranzacie autonom este o tranzacie independent lansat de alt tranzacie, numit tranzacie principal. Tranzacia autonom permite suspendarea tranzaciei principale, executarea de comenzi SQL, permanentizarea (commit) i anularea (rollback) acestor operaii. Odat nceput, tranzacia autonom este independent, n sensul c nu partajeaz blocri, resurse sau dependene cu tranzacia principal. n felul acesta, o aplicaie nu trebuie s cunoasc operaiile autonome ale unei proceduri, iar procedura nu trebuie s cunoasc nimic despre tranzaciile aplicaiei. Tranzacia autonom are totui toate funcionalitile unei tranzacii obinuite (permite cereri paralele, procesri distribuite etc.). Pentru definirea unei tranzacii autonome se utilizeaz clauza PRAGMA AUTONOMOUS_TRANSACTION care informeaz compilatorul PL/SQL c trebuie s marcheze o rutin ca fiind autonom. Prin rutin se nelege: bloc anonim de cel

Programare n PL/SQL

325

mai nalt nivel (nu imbricat); procedur sau funcie local, independent sau mpachetat; metod a unui tip obiect; declanator baz de date. Codul PRAGMA AUTONOMOUS_TRANSACTION se specific n partea declarativ a rutinei. Codul PRAGMA AUTONOMOUS_TRANSACTION marcheaz numai rutine individuale ca fiind independente. Nu pot fi marcate toate subprogramele unui pachet sau toate metodele unui tip obiect ca autonome. Prin urmare, clauza nu poate s apar n partea de specificaie a unui pachet. Observaii:

Declanatorii autonomi, spre deosebire de cei clasici pot conine comenzi LCD (de exemplu, COMMIT, ROLLBACK). Excepiile declanate n cadrul tranzaciilor autonome genereaz un rollback la nivel de tranzacie i nu la nivel de instruciune. Cnd se intr n seciunea executabil a unei tranzacii autonome, tranzacia principal este suspendat.

Cu toate c o tranzacie autonom este iniiat de alt tranzacie, ea nu este o tranzacie imbricat deoarece: nu partajeaz resurse cu tranzacia principal; nu depinde de tranzacia principal (de exemplu, dac tranzacia principal este anulat, atunci tranzaciile imbricate sunt de asemenea anulate, n timp ce tranzacia autonom nu este anulat); schimbrile permanentizate din tranzacii autonome sunt vizibile imediat altor tranzacii, pe cnd cele din tranzacii imbricate sunt vizibile doar dup ce tranzacia principal este permanentizat.

6. Modularizarea aplicaiilor prin utilizarea pachetelor


Pachetul (package) permite ncapsularea ntr-o unitate logic n baza de date a procedurilor, funciilor, cursoarelor, tipurilor, constantelor, variabilelor i excepiilor. Pachetele sunt uniti de program care sunt compilate, depanate i testate. Ele reprezint obiecte ale bazei de date care grupeaz tipuri, obiecte i subprograme PL/SQL cu o legtur logic ntre ele. Pachetele stocate permit gruparea procedurilor i funciilor nrudite, precum i partajarea variabilelor de ctre acestea. mpachetarea subprogramelor prezint beneficii importante. Cnd este referit un pachet (cnd este apelat pentru prima dat o construcie a pachetului), ntregul pachet este ncrcat n zona global sistem (SGA) i este pregtit pentru execuie. Plasarea pachetului n SGA prezint avantajul vitezei de execuie, deoarece server-ul nu mai trebuie s aduc informaia despre pachet de pe disc, aceasta fiind deja n memorie. Prin urmare, apeluri ulterioare ale unor construcii din acelai pachet nu solicit operaii I/O de pe disc.

326

PROGRAMARE AVANSAT N ORACLE9i

Dac apar proceduri i funcii nrudite care trebuie s fie executate mpreun, este convenabil ca acestea s fie grupate ntr-un pachet stocat. Este de remarcat c n memorie exist o singur copie a unui pachet, pentru toi utilizatorii. Spre deosebire de subprograme, pachetele nu pot fi apelate, nu pot transmite parametri i nu pot fi ncuibrite. n mod uzual un pachet are dou pri, fiecare fiind stocat separat n dicionarul datelor. Specificaia pachetului (package specification) reprezint partea vizibil, adic interfaa cu aplicaii sau cu alte uniti program. n aceast parte se descrie tot ce este necesar utilizatorului pentru a folosi pachetul. Se declar tipuri, constante, variabile, excepii, cursoare i subprograme. Corpul pachetului (package body) reprezint partea acuns, mascat de restul aplicaiei, adic realizarea specificaiei. Corpul definete cursoare i subprograme, implementnd specificaia. Obiectele coninute n corpul pachetului sunt fie private, fie publice. Prin urmare, specificaia definete interfaa utilizatorului cu pachetul, iar corpul pachetului conine codul care implementeaz operaiile definite n specificaie. Crearea unui pachet se face n dou etape care corespund crerii prilor acestuia. Un pachet poate cuprinde fie doar partea de specificaie, fie att specificaia, ct i corpul pachetului. n cazul n care conine doar specificaia, pachetul va cuprinde numai definiii de tipuri i declaraii de date. Corpul pachetului poate fi schimbat fr a fi necesar schimbarea specificaiei pachetului. Dac specificaia este schimbat, aceasta invalideaz automat corpul pachetului, deoarece corpul depinde de specificaie. Specificaia i corpul pachetului sunt uniti compilate separat. Corpul unui pachet poate fi compilat doar dup ce specificaia acestuia a fost compilat cu succes. Un pachet are urmtoarea form general: CREATE PACKAGE nume_pachet {IS | AS} -- specificaia /* interfaa utilizator, care conine: declaraii de tipuri i obiecte publice, specificaii de subprograme */ END [nume_pachet]; CREATE PACKAGE BODY nume_pachet {IS | AS} -- corpul /* implementarea, care conine: declaraii de obiecte i tipuri private, corpuri de subprograme specificate n partea de interfa */ [BEGIN] /* instruciuni de iniializare, executate o singur dat, atunci cnd pachetul este invocat prima oar ntr-o sesiune a unui utilizator */ END [nume_pachet];

Specificaia unui pachet

Programare n PL/SQL

327

Specificaia unui pachet cuprinde declararea procedurilor, funciilor, constantelor, variabilelor i excepiilor care pot fi accesibile utilizatorilor prin intermediul pachetului, adic declararea obiectelor de tip PUBLIC din pachet. Acestea pot fi utilizate n proceduri sau comenzi care nu aparin pachetului, dar care au privilegiul EXECUTE asupra acestuia. Variabilele declarate n specificaia unui pachet sunt globale pachetului i sesiunii. Ele sunt iniializate implicit cu valoarea null, dac nu este specificat explicit o alt valoare. Crearea specificaiei se face prin comanda: CREATE [OR REPLACE ] PACKAGE [schema.]nume_pachet [AUTHID {CURRENT_USER | DEFINER} ] {IS | AS} specificaie_pachet; Opiunea specificaie_pachet poate include declaraii de tipuri, variabile, cursoare, excepii, funcii, proceduri, clauze PRAGMA etc. n seciunea declarativ, un obiect trebuie s fie declarat nainte de a fi referit. Opiunea OR REPLACE se specific dac exist deja corpul pachetului. Clauza AUTHID specific faptul c subprogramele pachetului se execut cu drepturile proprietarului (implicit) sau ale utilizatorului curent. De asemenea, aceast clauz precizeaz dac referinele la obiecte sunt rezolvate n schema proprietarului pachetului sau n cea a utilizatorului curent.

Corpul unui pachet


Corpul unui pachet conine codul PL/SQL pentru obiectele declarate n specificaia acestuia i obiectele private ale pachetului. De asemenea, corpul poate include o seciune declarativ n care sunt specificate definiii locale de tipuri, variabile, constante, proceduri i funcii. Obiectele private sunt vizibile numai n interiorul corpului pachetului i pot fi accesate numai de ctre funciile i procedurile din pachetul respectiv. Corpul pachetului este opional i nu este necesar s fie creat dac specificaia pachetului nu conine declaraii de proceduri sau funcii. Este important ordinea n care subprogramele sunt definite n interiorul corpului pachetului. O variabil trebuie declarat nainte de a fi referit de alt variabil sau subprogram, iar un subprogram privat trebuie declarat sau definit nainte de a fi apelat de alte subprograme. Declaraiile publice (interfaa operaional) sunt vizibile aplicaiei, spre deosebire de declaraiile private (implementarea efectiv a corpului) care sunt mascate aplicaiei. Comanda care permite crearea corpului unui pachet are sintaxa: CREATE [OR REPLACE] PACKAGE BODY [schema.]nume_pachet {IS | AS} corp_pachet;

328

PROGRAMARE AVANSAT N ORACLE9i

Un pachet este instaniat atunci cnd este apelat prima dat. Aceasta presupune c pachetul este citit de pe disc, adus n memorie i este executat codul compilat al subprogramului apelat. De asemenea, n acest moment se aloc memorie tuturor variabilelor definite n pachet. n multe cazuri este necesar s se fac o iniializare atunci cnd pachetul este instaniat prima dat ntr-o sesiune. Aceasta se realizeaz prin adugarea unei seciuni de iniializare (opional) n corpul pachetului, seciune ncadrat ntre cuvintele cheie BEGIN i END. Seciunea conine un cod de iniializare care este executat atunci cnd pachetul este invocat pentru prima dat. Crearea pachetului face ca acesta s fie disponibil utilizatorului care l-a creat i oricrui alt utilizator ce deine privilegiul EXECUTE. Referina la un tip sau obiect specificat n pachet se face prefixnd numele acestuia cu numele pachetului. n corpul pachetului, obiectele din specificaie pot fi referite fr a le prefixa cu numele pachetului. Procesul de creare a specificaei i corpului unui pachet urmeaz un algoritm similar celui ntlnit n crearea subprogramelor PL/SQL independente. n ambele cazuri sunt verificate erorile sintactice i semantice, iar modulul este depus n dicionarul datelor. Dup ce a fost depus corpul pachetului, sunt verificate instruciunile SQL individuale, adic se cerceteaz dac obiectele referite exist i dac utilizatorul le poate accesa. Sunt comparate declaraiile de subprograme din specificaia pachetului cu cele din corpul pachetului (dac au acelai numr i tip de parametri). Orice eroare detectat la compilarea specificaiei sau a corpului pachetului este marcat n dicionarul datelor. Dup ce specificaia i corpul pachetului sunt compilate, ele devin obiecte n schema curent. n vizualizarea USER_OBJECTS din dicionarul datelor, vor fi create dou noi linii: OBJECT_TYPE PACKAGE PACKAGE BODY OBJECT NAME nume_pachet nume_pachet

Modificarea i suprimarea pachetelor


Modificarea unui pachet presupune recompilarea sa (pentru a putea modifica metoda de acces i planul de execuie) i se realizeaz prin comanda: ALTER PACKAGE [schema.]nume_pachet COMPILE [ {PACKAGE | BODY} ]; Schimbarea corpului pachetului nu cere recompilarea construciilor dependente, n timp ce schimbrile n specificaia acestuia solicit recompilarea fiecrui subprogram stocat care face referin la pachetul respectiv. Din acest motiv, este bine ca specificaia s conin ct mai puine construcii. Dac se dorete modificarea sursei, utilizatorul poate recrea pachetul (folosind opiunea OR REPLACE) nlocuindu-l pe cel existent. Suprimarea unui pachet se realizeaz prin comanda:

Programare n PL/SQL

329

DROP PACKAGE [schema.]nume_pachet [ {PACKAGE | BODY} ]; Dac n cadrul comenzii apare opiunea BODY, este distrus doar corpul pachetului, n caz contrar sunt suprimate att specificaia, ct i corpul acestuia. Dac pachetul este suprimat, toate obiectele dependente de acesta devin invalide. Dac este distrus numai corpul, toate obiectele dependente de acesta rmn valide. n schimb, nu pot fi apelate subprogramele declarate n specificaia pachetului sau cele dependente de acestea, pn cnd nu este recreat corpul pachetului. Pentru ca un utilizator s poat suprima un pachet trebuie fie ca pachetul s aparin schemei utilizatorului, fie ca utilizatorul s aib privilegiul sistem DROP ANY PROCEDURE. Apelarea unui pachet se realizeaz n funcie de mediul (SQL sau PL/SQL) care solicit un obiect din pachetul respectiv. 1) n PL/SQL se face prin referirea: nume_pachet.nume_component [ (list_de_argumente) ];

2) n SQL se face prin comanda:


EXECUTE nume_pachet.nume_component [ (list_de_argumente) ]. Exemplu: S se creeze un pachet ce include o procedur prin care se verific dac o combinaie specificat dintre atributele cod_artist i stil este o combinaie care exist n tabelul opera.
CREATE PACKAGE verif_pachet IS PROCEDURE verifica (p_idartist IN opera.cod_artist%TYPE, p_stil IN opera.stil%TYPE); END verif_pachet; / CREATE OR REPLACE PACKAGE BODY verif_pachet IS i NUMBER := 0; CURSOR opera_cu IS SELECT cod_artist, stil FROM opera; TYPE opera_table_tip IS TABLE OF opera_cu%ROWTYPE INDEX BY BINARY INTEGER; art_stil opera_table_tip; PROCEDURE verifica (p_idartist IN opera.cod_artist%TYPE, p_stil IN opera.stil%TYPE) IS BEGIN FOR k IN art_stil.FIRST..art_stil.LAST LOOP IF p_idartist = art_stil(k).cod_artist AND p_stil = art_stil(k).stil THEN RETURN;

330

PROGRAMARE AVANSAT N ORACLE9i

END IF; END LOOP; RAISE_APPLICATION_ERROR(-20777,'combinatie eronata'); END verifica; BEGIN FOR ope_in IN opera_cu LOOP art_stil(i) := ope_in; i := i+1; END LOOP; END verif_pachet; /

Apelarea n PL/SQL a unui obiect (verifica) din pachet se face prin:


verif_pachet.verifica (7935, 'impresionism');

Utilizarea n SQL a unui obiect (verifica) din pachet se face prin:


EXECUTE verif_pachet.verifica (7935, 'impresionism');

Una dintre posibilitile interesante oferite de pachetele PL/SQL este aceea de a crea proceduri/funcii overload. Acest tip de programare este folositor atunci cnd este necesar un singur subprogram care s execute aceeai operaie pe obiecte de tipuri diferite (diferite tipuri de parametri de intrare). Atunci cnd este apelat un subprogram overload sistemul decide, pe baza tipului i numrului de parametri, instana care trebuie s fie executat. Numai subprogramele locale sau care aparin unui pachet pot fi overload. Observaii:

Un declanator nu poate apela o procedur sau o funcie ce conine una dintre comenzile COMMIT, ROLLBACK, SAVEPOINT. Prin urmare, pentru flexibilitatea apelului (de ctre declanatori) subprogramelor coninute n pachete, trebuie verificat c nici una dintre procedurile sau funciile pachetului nu conin aceste comenzi. Procedurile i funciile coninute ntr-un pachet pot fi referite din fiiere SQL*Plus, din subprograme stocate PL/SQL, din aplicaii client (de exemplu, Oracle Forms sau Power Builder), din declanatori (baz de date), din programe aplicaie scrise n limbaje LG3. ntr-un pachet nu pot fi referite variabile gazd. ntr-un pachet, mai exact n corpul acestuia, sunt permise declaraii forward. Posibilitile oferite de aceste declaraii au fost comentate n capitolul destinat subprogramelor.

Dac un subprogram ce aparine unui pachet este apelat de un subprogram stand-alone trebuie remarcat c: dac se schimb doar corpul pachetului, subprogramul care refer o construcie a pachetului rmne valid; dac specificaia pachetului se schimb, atunci att subprogramul care refer o construcie a pachetului, precum i corpul pachetului sunt invalidate. Dac un subprogram stocat referit de un pachet se schimb, atunci ntregul

Programare n PL/SQL

331

corp al pachetului este invalidat, dar specificaia pachetului rmne valid.

Pachete predefinite
PL/SQL conine pachete predefinite (care sunt deja compilate n baza de date) utilizate pentru dezvoltarea de aplicaii. Aceste pachete adaug noi funcionaliti limbajului, precum protocoale de comunicaie, acces la fiierele sistemului etc. Apelarea unor proceduri din aceste pachete se face prin prefixarea numelui procedurii cu numele pachetului. Dintre cele mai importante pachete predefinite se remarc: DBMS_OUTPUT (permite afiarea de informaii); DBMS_DDL (furnizeaz accesul la anumite comenzi LDD care pot fi folosite n programe PL/SQL); UTL_FILE (permite citirea din fiierele sistemului de operare, respectiv scrierea n astfel de fiiere); UTL_HTTP (folosete protocolul HTTP pentru accesarea din PL/SQL a datelor publicate pe Internet); UTL_TCP (permite aplicaiilor PL/SQL s comunice cu server-e externe utiliznd protocolul TCP/IP); DBMS_SQL (acceseaz baza de date folosind SQL dinamic); DBMS_JOB (permite planificarea pentru execuie a programelor PL/SQL i execuia acestora); DBMS_PIPE (permite operaii de comunicare ntre dou sau mai multe procese conectate la aceeai instan Oracle); DBMS_LOCK (permite folosirea exclusiv sau partajat a unei resurse); DBMS_SNAPSHOT (permite exploatarea clieelor); DBMS_UTILITY (ofer utiliti DBA, analizeaz obiectele unei scheme particulare, verific dac server-ul lucreaz n mod paralel etc.); DBMS_LOB (realizeaz accesul la date de tip LOB, permind compararea datelor LOB, adugarea de date la un LOB, copierea datelor dintr-un LOB n altul, tergerea unor poriuni din date LOB, deschiderea, nchiderea i regsirea de informaii din date BFILE etc). DBMS_STANDARD este un pachet predefinit fundamental prin care se declar tipurile, excepiile, subprogramele care pot fi utilizate automat n toate programele PL/SQL. Coninutul pachetului este vizibil tuturor aplicaiilor. Pentru referirea componentelor sale nu este necesar prefixarea cu numele pachetului. De exemplu, utilizatorul poate folosi n aplicaia sa ori de cte ori are nevoie funcia ABS (x) din pachetul DBMS_STANDARD, care reprezint valoarea absolut a numrului x, fr a prefixa numele funciei cu numele pachetului.

Pachetul DBMS_OUTPUT
Un pachet frecvent utilizat este DBMS_OUTPUT, care permite afiarea de informaii atunci cnd se execut un program PL/SQL. Pachetul este util pentru depanarea procedurilor stocate i a declanatorilor sau pentru generarea de rapoarte.

332

PROGRAMARE AVANSAT N ORACLE9i

DBMS_OUTPUT lucreaz cu un buffer (coninut n SGA) n care poate fi scris informaie utiliznd procedurile PUT, PUT_LINE i NEW_LINE. Aceast informaie poate fi regsit folosind procedurile GET_LINE i GET_LINES. Procedura DISABLE dezactiveaz toate apelurile la pachetul DBMS_OUTPUT (cu excepia procedurii ENABLE) i cur buffer-ul de orice informaie. Inserarea n buffer a unui sfrit de linie se face prin procedura NEW_LINE. Procedura PUT depune (scrie) informaie n buffer, informaie care poate fi de tip NUMBER, VARCHAR2 sau DATE. PUT_LINE are acelai efect ca procedura PUT, dar insereaz i un sfrit de linie. Procedurile PUT i PUT_LINE sunt overload, astfel nct informaia poate fi scris n format nativ. Procedura GET_LINE regsete o singur linie de informaie (de dimensiune maxim 255) din buffer, dar sub form de ir de caractere. Procedura GET_LINES regsete mai multe linii (nr_linii) din buffer i le depune ntr-un tablou (nume_tab) PL/SQL care are elemente de tip ir de caractere. Valorile sunt plasate n tabel ncepnd cu linia zero. Specificaia este urmtoarea:
TYPE string255_table IS TABLE OF VARCHAR2(255) INDEX BY BINARY_INTEGER; PROCEDURE GET_LINES (nume_tab OUT string255_table, nr_linii IN OUT INTEGER);

Parametrul nr_linii este i parametru de tip OUT, deoarece numrul liniilor solicitate poate s nu corespund cu numrul de linii din buffer. De exemplu, pot fi solicitate 10 linii, iar n buffer s fie doar 6 linii. Atunci doar primele 6 linii din tabel sunt returnate. Dezactivarea referirilor la pachet se poate realiza prin procedura DISABLE, iar activarea referirilor se face cu ajutorul procedurii ENABLE. Aceasta are un parametru de intrare opional, dimensiunea buffer-ului, care implicit este 2000. Procedura ENABLE poate fi apelat de mai multe ori ntr-o sesiune, iar dimensiunea buffer-ului va fi setat la cea mai mare valoare dat de aceste apeluri. Prin urmare, dimensiunea nu este ntotdeauna dat de ultimul apel la procedur. Exemplu: Urmtorul exemplu plaseaz n buffer (apelnd de trei ori procedura PUT) toate informaiile ntr-o singur linie.
DBMS_OUTPUT.PUT(:opera.valoare||:opera.cod_artist); DBMS_OUTPUT.PUT(:opera.cod_opera); DBMS_OUTPUT.PUT(:opera.cod_galerie);

Informaia respectiv va fi gsit printr-un singur apel GET_LINE, dac aceste trei comenzi sunt urmate de:
DBMS_OUTPUT.NEW_LINE;

Altfel, nu se va vedea nici un efect al acestor comenzi deoarece PUT plaseaz informaia n buffer, dar nu adaug sfrit de linie. Atunci cnd este utilizat pachetul DBMS_OUTPUT, pot s apar erorile buffer overflow i line length overflow. Tratarea acestor erori se face apelnd

Programare n PL/SQL

333

procedura RAISE_APPLICATION_ERROR din pachetul DBMS_STANDARD.

Pachetul DBMS_SQL
Pachetul DBMS_SQL permite folosirea dinamic a comenzilor SQL n proceduri stocate sau n blocuri anonime i analiza gramatical a comenzilor LDD. Aceste comenzi nu sunt ncorporate n programul surs, ci sunt depuse n iruri de caractere. O comand SQL dinamic este o instruciune SQL care conine variabile ce se pot schimba n timpul execuiei. Pot fi utilizate instruciuni SQL dinamice pentru: a crea o procedur care opereaz asupra unui tabel al crui nume nu este cunoscut dect n momentul execuiei; a scrie i executa o comand LDD; a scrie i executa o comand GRANT, ALTER SESSION etc. n PL/SQL aceste comenzi nu pot fi executate static. Pachetul DBMS_SQL permite, de exemplu, ca ntr-o procedur stocat s apar comanda DROP TABLE. Evident, folosirea acestui pachet pentru a executa comenzi LDD poate genera interblocri. De exemplu, dac pachetul este utilizat pentru a terge o procedur care, ns, este folosit ulterior tergerii ei. SQL dinamic suport toate tipurile de date SQL, dar nu i pe cele specifice PL/SQL. Unica excepie o constituie faptul c o nregistrare PL/SQL poate s apar n clauza INTO a comenzii EXECUTE IMMEDIATE. Orice comand SQL (pentru a fi executat) trebuie s treac prin nite etape, cu observaia c unele dintre acestea pot fi evitate. Etapele presupun: analizarea gramatical a comenzii, adic verificarea sintactic a comenzii, validarea acesteia, asigurarea c toate referinele la obiecte sunt corecte i c exist privilegiile referitoare la acele obiecte (parse); obinerea de valori pentru variabilele de legtur din comanda SQL (binding variables); executarea comenzii (execute); selectarea liniilor rezultatului (se refer la cereri, nu la operaii de reactualizare); ncrcarea acestor linii (fetch). Dintre subprogramele pachetului DBMS_SQL, care permit implementarea etapelor amintite anterior se remarc: OPEN_CURSOR (deschide un nou cursor, adic se stabilete o zon de memorie n care este procesat comanda SQL); PARSE (stabilete validitatea comenzii SQL, adic instruciunea este verificat sintactic i asociat cursorului deja deschis); BIND VARIABLE (leag valoarea dat de variabila corespunztoare din instruciunea SQL analizat); EXECUTE (execut comanda SQL i returneaz numrul de linii procesate); FETCH_ROWS (regsete o linie pentru un cursor specificat, iar pentru mai multe linii se folosete un LOOP);

334

PROGRAMARE AVANSAT N ORACLE9i

CLOSE_CURSOR (nchide cursorul specificat).

Exemplu: S se construiasc o procedur care folosete SQL dinamic pentru a terge liniile unui tabel specificat (num_tab). Subprogramul furnizeaz ca rezultat numrul liniilor terse (nr_lin).
CREATE OR REPLACE PROCEDURE sterge_linii (num_tab IN VARCHAR2, nr_lin OUT NUMBER) AS nume_cursor INTEGER; BEGIN nume_cursor := DBMS_SQL.OPEN_CURSOR; DBMS_SQL.PARSE (nume_cursor, 'DELETE FROM ' || num_tab, DBMS_SQL.V7); nr_lin := DBMS_SQL.EXECUTE (nume_cursor); DBMS_SQL.CLOSE_CURSOR (nume_cursor); END;

Prin argumentul DBMS_SQL.V7 este specificat versiunea 7 a sistemului i el reprezint modul n care Oracle trateaz execuia comenzilor SQL. tergerea efectiv a liniilor tabelului opera se realizeaz prin secvena:
VARIABLE linii_sterse NUMBER EXECUTE sterge_linii ('opera', :linii_sterse) PRINT linii_sterse

Pentru a executa o instruciune SQL dinamic poate fi utilizat i comanda EXECUTE IMMEDIATE, care va fi analizat n capitolul urmtor. Comanda poate fi folosit att pentru interogrile ce returneaz o singur linie, ct i pentru cele care returneaz mai multe linii.

Pachetul DBMS_DDL
Pachetul DBMS_DDL furnizeaz accesul la anumite comenzi LDD care pot fi folosite n subprograme PL/SQL stocate. n felul acesta pot fi accesate (n PL/SQL) comenzile ALTER sau ANALYZE. Pachetul include procedura ALTER_COMPILE care permite recompilarea explicit a unui program modificat (procedur, funcie, declanator, pachet, corp de pachet). Ea are urmtoarea form sintactic: ALTER_COMPILE (tip_obiect, nume_schema, nume_obiect); Procedura este echivalent cu instruciunea SQL: ALTER PROCEDURE | FUNCTION | PACKAGE [schema.] nume COMPILE [BODY] Cu ajutorul procedurii ANALYZE_OBJECT poate fi analizat un obiect de tip table, cluster sau index. Procedura furnizeaz statistici referitoare la obiectele amintite. De exemplu, se pot obine numrul liniilor sau numrul de blocuri ale unui tabel, lungimea medie a unei linii, numrul valorilor distincte ale unei coloane, numrul elementelor null dintr-o coloan, distribuia datelor (histograma) etc.

Programare n PL/SQL

335

Procedura are forma sintactic: ANALYZE_OBJECT (tip_obiect, nume_schema, nume_obiect, metoda, numr_linii_estimate, procent, opiune_metoda, nume_partiie); Metodele care pot fi utilizate n procedura ANALYZE_OBJECT sunt COMPUTE, ESTIMATE sau DELETE. Prin aceste metode se cuantific distribuia datelor i caracteristicile de stocare. DELETE determin tergerea statisticilor (depuse n DD) referitoare la obiectul analizat. COMPUTE calculeaz statistici referitoare la un obiect analizat i le depune n DD, iar ESTIMATE estimeaz statistici. Statisticile calculate sau estimate sunt utilizate pentru optimizarea planului de execuie a comenzilor SQL care acceseaz obiectele analizate. Procedura este echivalent cu instruciunea: ANALYZE TABLE | CLUSTER | INDEX [nume_schema.]nume_obiect [metoda] STATISTICS [SAMPLE n] [ROWS | PERCENT] ] Dac nume_schema este null, atunci se presupune c obiectul aparine schemei curente. Pentru tip_obiect sunt permise variantele table, index sau cluster. Parametrul procent reprezint procentajul liniilor de estimat i este ignorat dac este specificat numrul liniilor de estimat. n mod implicit, ultimele patru argumente ale procedurii iau valoarea null. Argumentul opiune_metoda poate avea forma: [FOR TABLE] [FOR ALL INDEXES] [FOR ALL [INDEXED] COLUMNS] [SIZE n] Pentru metoda ESTIMATE trebuie s fie prezent una dintre aceste opiuni. Exemplu: Utiliznd pachetul DBMS_DDL i metoda COMPUTE, s se creeze o procedur care analizeaz un obiect furnizat ca parametru.
CREATE OR REPLACE PROCEDURE analiza (p_obiect_tip IN VARCHAR2, p_obiect_nume IN VARCHAR2) IS BEGIN DBMS_DDL.ANALYZE_OBJECT(p_obiect_tip, USER, UPPER(p_obiect_nume), 'COMPUTE'); END;

Procedura se testeaz (relativ la tabelul opera) n felul urmtor:


EXECUTE analiza ('TABLE', 'opera') SELECT LAST_ANALYZED FROM USER_TABLES WHERE TABLE_NAME = 'opera';

Pachetul DBMS_JOB
Pachetul DBMS_JOB este utilizat pentru planificarea programelor PL/SQL n vederea execuiei. Cu ajutorul acestui pachet se pot executa programe PL/SQL la

336

PROGRAMARE AVANSAT N ORACLE9i

momente determinate de timp, se pot terge sau suspenda programe din lista de planificri pentru execuie, se pot rula programe de ntreinere n timpul perioadelor de utilizare sczut etc. Dintre subprogramele acestui pachet se remarc: SUBMIT adaug un nou job n coada de ateptare a job-urilor; REMOVE terge un job specificat din coada de ateptare a job-urilor; RUN execut imediat un job specificat; BROKEN dezactiveaz execuia unui job care este marcat ca broken (implicit, orice job este not broken, iar un job marcat broken nu se execut); CHANGE modific argumentele WHAT, NEXT_DATE, INTERVAL; WHAT modific descrierea unui job specificat; NEXT_DATE modific momentul urmtoarei execuii a unui job; INTERVAL modific intervalul ntre diferite execuii ale unui job. Fiecare dintre subprogramele pachetului are argumente specifice. De exemplu, procedura DBMS_JOB.SUBMIT are ca argumente: JOB de tip OUT, un identificator pentru job (BINARY_INTEGER); WHAT de tip IN, codul PL/SQL care va fi executat ca un job (VARCHAR2); NEXT_DATE de tip IN, data urmtoarei execuii a job-ului (implicit este SYSDATE); INTERVAL de tip IN, funcie care furnizeaz intervalul dintre execuiile job-ului (VARCHAR2, implicit este null); NO_PARSE de tip IN, variabil logic care indic dac job-ul trebuie analizat gramatical (BOOLEAN, implicit este FALSE). Dac unul dintre parametri WHAT, INTERVAL sau NEXT_DATE are valoarea null, atunci este folosit ultima valoare asignat acestora. Exemplu: S se utilizeze pachetul DBMS_JOB pentru a plasa pentru execuie n coada de ateptare a job-urilor, procedura verifica din pachetul verif_pachet.
VARIABLE num_job NUMBER BEGIN DBMS_JOB.SUBMIT( job => :num_job, what => 'verif_pachet.verifica(8973,''impresionism'');' next_date => TRUNC(SYSDATE+1), interval => 'TRUNC(SYSDATE+1)'); COMMIT; END; / PRINT num_job

Vizualizarea DBA_JOBS din dicionarul datelor furnizeaz informaii referitoare la starea job-urilor din coada de ateptare, iar vizualizarea

Programare n PL/SQL

337

DBA_JOBS_RUNNING conine informaii despre job-urile care sunt n curs de execuie. Vizualizrile pot fi consultate doar de utilizatorii care au privilegiul SYS.DBA_JOBS. Exemplu:
SELECT JOB, LOG_USER, NEXT_DATE, BROKEN, WHAT FROM DBA_JOBS;

Pachetul UTL_FILE
Pachetul UTL_FILE permite programului PL/SQL citirea din fiierele sistemului de operare, respectiv scrierea n aceste fiiere. El este utilizat pentru exploatarea fiierelor text. Pachetul proceseaz fiierele ntr-o manier clasic: verific dac fiierul este deschis (funcia IS_OPEN); dac fiierul nu este deschis, l deschide i returneaz un handler de fiier (de tip UTL_FILE.FILE_TYPE) care va fi utilizat n urmtoarele operaii I/O (funcia FOPEN); proceseaz fiierul (citire/scriere din/n fiier); nchide fiierul (procedura FCLOSE). Prin procedura GET_LINE, pachetul UTL_FILE citete o linie de text din fiierul deschis pentru citire i o plaseaz ntr-un buffer de tip ir de caractere, iar prin procedurile PUT i PUT_LINE scrie un text din buffer n fiierul deschis pentru scriere sau adugare. Utilizarea componentelor acestui pachet pentru procesarea fiierelor sistemului de operare poate declana urmtoarele excepii: INVALID_PATH numele sau locaia fiierului sunt invalide; INVALID_MODE parametrul OPEN_MODE (prin care se specific dac fiierul este deschis pentru citire, scriere, adugare) este invalid; INVALID_FILEHANDLE handler-ul de fiier obinut n urma deschiderii este invalid; INVALID_OPERATION operaie invalid asupra fiierului; READ_ERROR o eroare a sistemului de operare a aprut n timpul operaiei de citire; WRITE_ERROR o eroare a sistemului de operare a aprut n timpul operaiei de scriere; INTERNAL_ERROR o eroare nespecificat a aprut n PL/SQL.

Pachetele DBMS_PIPE i DBMS_ALERT


Pachetul DBMS_PIPE permite operaii de comunicare ntre dou sau mai multe sesiuni conectate la aceeai baz de date. De exemplu, pachetul poate fi utilizat pentru comunicarea dintre o procedur stocat i un program Pro*C. Comunicarea se face prin conducte (pipe). O conduct este o zon de memorie utilizat de un proces pentru a transmite informaie altui proces. Informaia trimis prin conduct este depus ntr-un buffer din SGA. Toate informaiile din conduct

338

PROGRAMARE AVANSAT N ORACLE9i

sunt pierdute atunci cnd instana este nchis. Conductele sunt asincrone, ele opernd independent de tranzacii. Dac un anumit mesaj a fost transmis, nu exist nici o posibilitate de oprire a acestuia, chiar dac sesiunea care a trimis mesajul este derulat napoi (rollback). Pachetul DBMS_PIPE este utilizat pentru a trimite mesaje n conduct (DBMS_PIPE.SEND_MESSAGE), mesaje ce constau din date de tip VARCHAR2, NUMBER, DATE, RAW sau ROWID. Tipurile obiect definite de utilizator i coleciile nu sunt acceptate de acest pachet. De asemenea, pachetul poate realiza primirea de mesaje din conduct n buffer-ul local (DBMS_PIPE.RECEIVE_MESSAGE), accesarea urmtorului articol din buffer (DBMS_PIPE.UNPACK_MESSAGE), crearea unei noi conducte (DBMS_PIPE. CREATE_PIPE) etc. DBMS_ALERT este similar pachetului DBMS_PIPE, fiind utilizat tot pentru comunicarea dintre sesiuni conectate la aceeai baz de date. Exist totui cteva deosebiri eseniale. DBMS_ALERT asigur o comunicare sincron. Un mesaj trimis prin DBMS_PIPE este primit de ctre un singur destinatar (cititor), chiar dac exist mai muli pe conduct, pe cnd cel trimis prin DBMS_ALERT poate fi primit de mai muli cititori simultan. Dac dou mesaje sunt trimise printr-o conduct (nainte ca ele s fie citite), ambele vor fi primite de destinatar prin DBMS_PIPE. n cazul pachetului DBMS_ALERT, doar cel de al doilea mesaj va fi primit.

Pachete predefinite furnizate de Oracle9i


Oracle9i furnizeaz o varietate de pachete predefinite care simplific administrarea bazei de date i ofer noi funcionaliti legate de noile caracteristici ale sistemului. Dintre pachetele introduse n versiunea Oracle9i se remarc:

DBMS_REDEFINITION permite reorganizarea online a tabelelor; DBMS_LIBCACHE permite extragerea de comenzi SQL i PL/SQL dintr-o instan distant ntr-una una local (vor fi compilate local, dar nu executate); DBMS_LOGMNR_CDC_PUBLISH realizeaz captarea schimbrilor din tabelele bazei de date (identific datele adugate, modificate sau terse i editeaz aceste informaii ntr-o form utilizabil n aplicaii); DBMS_LOGMNR_CDC_SUBSCRIBE face posibil vizualizarea i interogarea schimbrilor din datele care au fost captate cu pachetul DBMS_LOGMNR_CDC_PUBLISH; DBMS_METADATA furnizeaz informaii despre obiectele bazei de date; DBMS_RESUMABLE permite setarea limitelor de spaiu i timp pentru o operaie specificat, operaia fiind suspendat dac sunt depite aceste limite; DBMS_XMLQUERY, DBMS_XMLSAVE, DBMS_XMLGEN permit prelucrarea i conversia datelor XML (XMLGEN convertete rezultatul

Programare n PL/SQL

339

unei cereri SQL n format XML, XMLQUERY este similar lui XMLGEN, doar c este scris n C, iar XMLSAVE face conversia din format XML n date ale bazei); DBMS_LDAP furnizeaz funcii i proceduri pentru accesarea datelor de pe server-e LDAP (Lightweight Directory Access Protocol); UTL_ENCODE permite codificarea i decodificarea ntr-un format standard a unei date de tip RAW, astfel nct datele s poat fi transferate ntre diferite gazde (host-uri); UTL_INADDR returneaz numele unei gazde locale sau distante a crei adres IP este cunoscut i reciproc, returneaz adresa IP a unei gazde creia i se cunoate numele (de exemplu, www.oracle.com); DBMS_AQELM furnizeaz proceduri i funcii pentru gestionarea configuraiei cozilor de mesaje asincrone prin e-mail i HTTP; DBMS_FGA asigur ntreinerea unor funcii de securitate; DBMS_FLASHBACK permite trecerea la o versiune a bazei de date corespunztoare unei uniti de timp specificate sau unui SCN (system change number) dat, n felul acesta putnd fi recuperate linii terse sau mesaje e-mail distruse; DBMS_TRANSFORM furnizeaz subprograme ce permit transformarea unui obiect (expresie SQL sau funcie PL/SQL) de un anumit tip (surs) ntr-un obiect avnd un tip (destinaie) specificat; DBMS_WM ofer funcionaliti legate de Workspace Manager. Gestiunea spaiului de lucru se refer la abilitatea bazei de date de a pstra diferite versiuni ale aceleai linii n unul sau mai multe spaii de lucru (virtuale), utilizatorii bazei putnd modifica independent aceste versiuni. De exemplu, pachetul ofer posibilitatea de a modifica descrierea unui punct de salvare, de a terge puncte de salvare, de a lista privilegiile pe care le are utilizatorul curent referitor la un anumit spaiu de lucru, de a determina dac exist conflicte ntre spaii de lucru etc.

8. Folosirea dinamic a comenzilor SQL n PL/SQL


SQL dinamic este o parte integrant a limbajului SQL care permite folosirea dinamic a comenzilor sale n proceduri stocate sau n blocuri anonime. Spre deosebire de comenzile statice, care nu se schimb n timp real, comenzile dinamice se schimb de la o execuie la alta. Comenzile dinamice SQL pot depinde de anumite valori de intrare furnizate de utilizator sau de procesarea realizat n programul aplicaie. Ele nu sunt incorporate n programul surs, ci sunt depuse n iruri de caractere. SQL dinamic este o tehnic de programare care permite construirea dinamic a comenzilor la momentul execuiei. Textul comenzii nu este cunoscut la compilare. De exemplu, se creeaz o procedur care opereaz asupra unui tabel al

340

PROGRAMARE AVANSAT N ORACLE9i

crui nume este cunoscut doar cnd se execut procedura. n momentul compilrii este cunoscut definiia tabelelor, dar nu i numele acestora. Exist aplicaii (de exemplu, legate de data warehouse) n care la fiecare unitate de timp (de exemplu, sfert de or) sunt generate noi tabele, toate avnd aceeai structur. Utilitatea tehnicii SQL dinamic este justificat de motive majore, dintre care se remarc: necesitatea de a executa n PL/SQL, comenzi SQL care nu pot fi apelate n codul PL/SQL (de exemplu, CREATE, DROP, GRANT, REVOKE, ALTER SESSION, SET ROLE); necesitatea unei flexibiliti n tratarea comenzilor (de exemplu, posibilitatea de a avea diferite condiii n clauza WHERE a comenzii SELECT); necunoaterea complet, la momentul implementrii, a comenzii SQL care trebuie executat. Pentru execuia dinamic a comenzilor SQL n PL/SQL exist dou tehnici: utilizarea pachetului DBMS_SQL; SQL dinamic nativ. Dac s-ar face o comparaie ntre SQL dinamic nativ i funcionalitatea pachetului DBMS_SQL, se poate sublinia c SQL dinamic nativ este mai uor de utilizat, mai rapid i are avantajul c suport tipuri definite de utilizator. Pachetul DBMS_SQL, n raport cu SQL dinamic nativ: poate fi folosit n programe client-side; suport comenzi SQL mai mari de 32 KB; permite ncrcarea nregistrrilor (procedura FETCH_ROWS ); accept comenzi cu clauza RETURNING pentru reactualizarea i tergerea de linii multiple; suport posibilitile oferite de comanda DESCRIBE (procedura DESCRIBE_COLUMNS); analizeaz validitatea unei comenzi SQL o singur dat (procedura PARSE), permind ulterior mai multe utilizri ale comenzii pentru diferite mulimi de argumente. SQL dinamic nativ a fost introdus n Oracle8i, asigurnd plasarea de comenzi SQL dinamic n codul PL/SQL. Comanda de baz utilizat pentru procesarea dinamic nativ a comenzilor SQL i a blocurilor PL/SQL este EXECUTE IMMEDIATE, care are urmtoarea sintax: EXECUTE IMMEDIATE ir_dinamic [INTO {def_variabila [, def_variabila ] | record} ] [USING [IN | OUT | IN OUT] argument_bind [, [IN | OUT | IN OUT] argument_bind ] ] [ {RETURNING | RETURN} INTO argument_bind [, argument_bind ] ]; Atributul ir_dinamic este o expresie (ir de caractere) care reprezint o comand SQL (fr caracter de terminare) sau un bloc PL/SQL (avnd caracter de

Programare n PL/SQL

341

continuare); def_variabila reprezint variabila n care se stocheaz valoarea coloanei selectate; record reprezint nregistrarea n care se depune o linie selectat; argument_bind, dac se refer la valori de intrare (IN) este o expresie (comand SQL sau bloc PL/SQL), iar dac se refer la valori de ieire (OUT) este o variabil ce va conine valoarea selectat de comanda SQL sau de blocul PL/SQL. Clauza INTO este folosit pentru cereri care ntorc o singur linie, iar clauza USING pentru a reine argumentele de legtur. Pentru procesarea unei cereri care returneaz mai multe linii sunt necesare instruciunile OPENFOR, FETCH i CLOSE. Prin clauza RETURNING sunt precizate variabilele care conin rezultatele. Observaii:

SQL dinamic suport toate tipurile SQL, dar nu accept tipuri de date specifice PL/SQL (unica excepie este tipul RECORD care poate s apar n clauza INTO). n subprogramele PL/SQL pot s fie executate dinamic comenzi SQL care se refer la obiecte aparinnd unei baze de date distante. n anumite situaii, o comand LDD poate crea o interblocare. De exemplu, o procedur poate genera o interblocare dac n corpul procedurii exist o comand care terge chiar procedura respectiv. Prin urmare, niciodat nu pot fi utilizate comenzile ALTER sau DROP referitoare la un subprogram sau pachet n timp ce se lucreaz cu pachetul sau subprogramul respectiv.

Exemplu: S se construiasc o procedur care poate terge orice tabel din baza de date. Numele tabelului ters este transmis ca parametru acestei proceduri.
CREATE PROCEDURE sterge_tabel (nume_tabel IN VARCHAR2) AS BEGIN EXECUTE IMMEDIATE 'DROP TABLE :tab' USING nume_tabel; END;

La execuia acestei proceduri va fi semnalat o eroare, deoarece nu pot fi utilizate variabile de legtur ca argument, pentru a transmite numele obiectelor dintr-o schem. Prin urmare comanda corect este:
EXECUTE IMMEDIATE 'DROP TABLE ' || nume_tabel;

Exemplu: Valoarea null nu poate s apar n clauza USING. Comanda urmtoare este incorect.
EXECUTE IMMEDIATE 'UPDATE opera SET valoare = :x' USING null;

Totui, dac este necesar folosirea valorii null, pot fi utilizate variabile neiniializate. O soluie pentru corectarea erorii anterioare este dat de secvena:
DECLARE val_null NUMBER;

342

PROGRAMARE AVANSAT N ORACLE9i

BEGIN EXECUTE IMMEDIATE 'UPDATE opera SET valoare = :x' USING val_null; END;

Exemplu: S se obin numrul operelor de art din muzeu a cror valoare depete o limit precizat (cerere single-row).
CREATE OR REPLACE FUNCTION numar_opera (val_opera NUMBER) RETURN NUMBER AS sir_cerere VARCHAR2(500); num_oper NUMBER; BEGIN sir_cerere := 'SELECT COUNT(*) FROM opera ' || 'WHERE valoare >= :alfa'; EXECUTE IMMEDIATE sir_cerere INTO num_oper USING val_opera; RETURN num_oper; END;

Exemplu: Exemplul care urmeaz furnizeaz o modalitate de utilizare corect a argumentelelor n clauza USING. a) Pentru comenzi SQL, asocierea cu argumentele de legtur (bind) din clauza USING este prin poziie.
sql_com := 'INSERT INTO alfa VALUES (:x, :x, :y, :x)'; EXECUTE IMMEDIATE sql_com USING a, a, b, a b) Pentru blocuri PL/SQL executate dinamic, asocierea cu argumentele bind din clauza USING se face prin nume. DECLARE x NUMBER := 7; y NUMBER := 23; v_bloc VARCHAR2(200); BEGIN v_bloc := 'BEGIN calcule(:a, :a, :b, :a); END;'; EXECUTE IMMEDIATE v_bloc USING x, y; END;

n exemplul care urmeaz va fi ilustrat modul de utilizare a comenzii EXECUTE IMMEDIATE pentru executarea comenzilor LDD, comenzilor LMD de reactualizare i a blocurilor PL/SQL anonime. irul care trebuie executat poate fi un literal inclus ntre apostrofuri (de exemplu, CREATE TABLE sau DROP TABLE) sau poate fi un ir de caractere (de exemplu, blocuri anonime). Caracterul ; nu trebuie inclus dect pentru blocurile anonime. Exemplu:

Programare n PL/SQL DECLARE v_sql_sir VARCHAR2(200); v_plsql_bloc VARCHAR2(200); BEGIN -- creare tabel EXECUTE IMMEDIATE 'CREATE TABLE model_tabel (col1 VARCHAR(30))'; FOR contor IN 1..10 LOOP v_sql_sir := 'INSERT INTO model_tabel VALUES (''Linia ' || contor || ''')'; EXECUTE IMMEDIATE v_sql_sir; END LOOP; -- tipareste continut tabel utilizand un bloc anonim v_plsql_bloc := 'BEGIN FOR cont IN (SELECT * FROM model_tabel) LOOP DBMS_OUTPUT.PUT_LINE (cont.col1); END LOOP; END;'; -- executie bloc anonim EXECUTE IMMEDIATE v_plsql_bloc; -- sterge tabel EXECUTE IMMEDIATE 'DROP TABLE model_tabel'; END;

343

Comanda EXECUTE IMMEDIATE poate fi utilizat i pentru execuia unor comenzi n care intervin variabile bind. Exemplul urmtor ilustreaz aceast situaie, marcnd i modul de folosire a clauzei USING. Exemplu:
DECLARE v_sql_sir VARCHAR2(200); v_plsql_bloc VARCHAR2(200); BEGIN v_sql_sir := 'INSERT INTO opera (cod_opera, titlu, valoare) VALUES (:cod, :descriere, :val)'; EXECUTE IMMEDIATE v_sql_sir USING 'c17', 'Modista', 15; v_plsql_bloc := 'BEGIN UPDATE artist SET nume = ''Gauguin'' WHERE cod_artist = :xx; END;'; EXECUTE IMMEDIATE v_plsql_bloc USING 'a37'; END;

Pentru executarea cererilor multiple este necesar o abordare similar celei descrise n cazul cursoarelor dinamice, prin utilizarea comenzilor OPENFOR,

344

PROGRAMARE AVANSAT N ORACLE9i

FETCH i CLOSE. n exemplul care urmeaz este prezentat maniera n care pot fi executate diferite cereri, utiliznd SQL dinamic nativ. Exemplu:
CREATE OR REPLACE PACKAGE nativ AS TYPE t_ref IS REF CURSOR; FUNCTION opera_cerere (p_clauza IN VARCHAR2) RETURN t_ref; FUNCTION opera_alta_cerere (p_stil IN VARCHAR2) RETURN t_ref; END nativ; CREATE OR REPLACE PACKAGE BODY nativ AS FUNCTION opera_cerere (p_clauza IN VARCHAR2) RETURN t_ref IS v_retur_cursor t_ref; v_sql_comanda VARCHAR2(500); BEGIN v_sql_comanda := 'SELECT * FROM opera ' || p_clauza; OPEN v_retur_cursor FOR v_sql_comanda; RETURN v_retur_cursor; END opera_cerere; FUNCTION opera_alta_cerere (p_stil IN VARCHAR2) RETURN t_ref IS v_retur_cursor t_ref; v_sql_comanda VARCHAR2(500); BEGIN v_sql_comanda := 'SELECT * FROM opera WHERE stil = :s'; OPEN v_retur_cursor FOR v_sql_comanda USING p_stil; RETURN v_retur_cursor; END opera_alta_cerere; END nativ; DECLARE v_opera opera%ROWTYPE; v_opera_cursor nativ.t_ref; BEGIN -- deschide cursor v_opera_cursor := nativ.opera_cerere ('WHERE valoare < 1000000'); -- parcurge cursor si tipareste rezultate DBMS_OUTPUT.PUT_LINE ('Urmatoarele opere au valoarea mai mica de un milion de dolari'); LOOP FETCH v_opera_cursor INTO v_opera; EXIT WHEN v_opera_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_opera.titlu || ' ' || v_opera.stil); END LOOP;

Programare n PL/SQL CLOSE v_opera_cursor;

345

-- se procedeaza similar pentru functia opera_alta_cerere -- deschide cursor v_opera_cursor := nativ.opera_alta_cerere ('impresionism'); -- parcurge cursor si tipareste rezultate DBMS_OUTPUT.PUT_LINE ('Urmatoarele opere de arta apartin impresionismului'); LOOP FETCH v_opera_cursor INTO v_opera; EXIT WHEN v_opera_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE (v_opera.titlu || ' ' || v_opera.valoare); END LOOP; CLOSE v_opera_cursor; END;

Comanda EXECUTE IMMEDIATE poate fi utilizat pentru cereri care ntorc o singur linie, cu sau fr variabile bind. Urmtorul exemplu prezint, n trei cazuri, modul de implementare a acestei posibiliti. Exemplu:
DECLARE v_sql_cerere VARCHAR2(200); v_galerie galerie%ROWTYPE; v_nume_galerie galerie.nume_galerie%TYPE; BEGIN -- selectare in variabila v_sql_cerere := 'SELECT nume_galerie ' || 'FROM galerie ' || 'WHERE cod_galerie = ''g73'''; EXECUTE IMMEDIATE v_sql_cerere INTO v_nume_galerie; DBMS_OUTPUT.PUT_LINE (v_nume_galerie); -- selectare in inregistrare, utilizand variabile bind v_sql_cerere := 'SELECT * ' || 'FROM galerie ' || 'WHERE nume_galerie = :nume_galerie'; EXECUTE IMMEDIATE v_sql_cerere INTO v_galerie USING v_nume_galerie; DBMS_OUTPUT.PUT_LINE (v_galerie.cladire); -- incarcarea unei noi linii va genera eroarea ORA-1422 v_sql_cerere := 'SELECT * FROM galerie'; EXECUTE IMMEDIATE v_sql_cerere INTO v_galerie; END;

346

PROGRAMARE AVANSAT N ORACLE9i

Structura Bulk bind permite executarea cererilor care returneaz mai multe linii, toate liniile returnate putnd fi obinute printr-o singur operaie. Puterea acestei structuri poate fi combinat cu facilitile oferite de SQL dinamic. Utiliznd comenzile FETCH, EXECUTE IMMEDIATE, FORALL, clauzele RETURNING INTO, COLLECT INTO i atributul cursor %BULK_ROWCOUNT se pot construi comenzi SQL care se execut dinamic utiliznd tehnica bulk bind. Comenzile vor avea o structur adaptat pentru rezolvarea dinamic a comenzilor SQL. n acest caz, comanda FETCH are forma sintactic: FETCH cursor_dinamic BULK COLLECT INTO variabila [, variabila ]; De remarcat c dac numrul variabilelor este mai mare dect numrul de coloane, Oracle va declana o eroare. Comanda FORALL va avea urmtoarea structur modificat: FORALL index IN lim_inf .. lim_sup EXECUTE IMMEDIATE ir_dinamic USING argument_bind | argument_bind(index) [, argument_bind | argument_bind(index) ] [ {RETURNING | RETURN} BULK COLLECT INTO argument_bind [, argument_bind ] ]; Atributul ir_dinamic accept comenzile INSERT, UPDATE i DELETE, dar nu comanda SELECT. Liniile de valori returnate de comanda EXECUTE IMMEDIATE pot fi depuse ntr-o colecie. Comanda i modific structura n urmtoarea manier: EXECUTE IMMEDIATE ir_dinamic [ [BULK COLLECT] INTO variabila [, variabila ] [USING argument_bind [, argument_bind ] ] [ {RETURNING | RETURN} BULK COLLECT INTO argument_bind [, argument_bind ] ]; Exemplu: Exemplul care urmeaz arat modul de utilizare a clauzei BULK COLLECT n comenzile FETCH i EXECUTE IMMEDIATE.
DECLARE TYPE opera_ref IS REF CURSOR; TYPE tab_num IS TABLE OF NUMBER; TYPE car_tab IS TABLE OF VARCHAR2(30); oper opera_ref; num1 tab_num; car1 car_tab; num2 tab_num; BEGIN OPEN oper FOR 'SELECT cod_opera, valoare FROM opera'; FETCH oper BULK COLLECT INTO car1, num1; CLOSE oper;

Programare n PL/SQL EXECUTE IMMEDIATE 'SELECT valoare FROM opera' BULK COLLECT INTO num2; END;

347

Numai comenzile INSERT, UPDATE i DELETE pot avea variabile bind ca argumente OUT. Exemplu:
DECLARE TYPE tab IS TABLE OF VARCHAR2(60); v_val tab; bun NUMBER := 100000; com_sql VARCHAR2(200); BEGIN com_sql := 'UPDATE opera SET valoare = :1 RETURNING titlu INTO :2'; EXECUTE IMMEDIATE com_sql USING bun RETURNING BULK COLLECT INTO v_val; END;

Pentru a utiliza variabile bind ca intrri ntr-o comand SQL (diferit de SELECT) se pot folosi comanda FORALL i clauza USING. Exemplu:
DECLARE TYPE num IS TABLE OF NUMBER; TYPE car IS TABLE OF VARCHAR2(30); num1 num; car1 car; BEGIN num1 := num(1, 2, 3, 4, 5); FORALL i IN 1..5 EXECUTE IMMEDIATE 'UPDATE opera SET val = val*1.1 WHERE cod_opera = :1 RETURNING titlu INTO :2' USING num1(i) RETURNING BULK COLLECT INTO car1; END;

SQL dinamic poate fi apelat i din cadrul altor limbaje. Limbajele C/C++ apeleaz SQL dinamic prin OCI (Oracle Call Interface) sau poate fi utilizat un precompilator Pro*C/C++ pentru adugarea comenzilor dinamice SQL la codul C. Limbajul Cobol poate utiliza comenzi dinamice SQL folosind un precompilator Pro*Cobol, iar limbajul Java prin intermediul lui JDBC (interfaa pentru conectarea limbajului la baze de date relaionale).

9. Gestiunea declanatorilor

348

PROGRAMARE AVANSAT N ORACLE9i

Un declanator (trigger) este un bloc PL/SQL care se execut automat ori de cte ori are loc un anumit eveniment declanator. Evenimentul poate consta din modificarea unui tabel sau a unei vizualizri, din aciuni sistem sau chiar anumite aciuni utilizator. Blocul PL/SQL poate fi asociat unui tabel, unei vizualizri, unei scheme sau unei baze de date. La fel ca i pachetele, declanatorii nu pot fi locali unui bloc sau unui pachet, ei trebuie depui ca obiecte independente n baza de date. Folosirea declanatorilor garanteaz faptul c atunci cnd o anumit operaie este efectuat, automat sunt executate nite aciuni asociate. Evident, nu trebuie introdui declanatori care ar putea s substituie funcionaliti oferite deja de sistem. De exemplu, nu are sens s fie definii declanatori care s implementeze regulile de integritate ce pot fi definite, mai simplu, prin constrngeri declarative.

Tipuri de declanatori
Declanatorii pot fi: la nivel de baz de date (database triggers); la nivel de aplicaie (application triggers). Declanatorii baz de date se execut automat ori de cte ori are loc: o aciune (comand LMD) asupra datelor unui tabel; o aciune (comand LMD) asupra datelor unei vizualizri; o comand LDD (CREATE, ALTER, DROP) referitoare la anumite obiecte ale schemei sau ale bazei; un eveniment sistem (SHUTDOWN , STARTUP); o aciune a utilizatorului (LOGON, LOGOFF); o eroare (SERVERERROR, SUSPEND). Declanatorii baz de date sunt de trei tipuri: declanatori LMD activai de comenzi LMD (INSERT, UPDATE sau DELETE) executate asupra unui tabel al bazei de date; declanatori INSTEAD OF activai de comenzi LMD executate asupra unei vizualizri (relaionale sau obiect); declanatori sistem activai de un eveniment sistem (oprirea sau pornirea bazei), de comenzi LDD (CREATE, ALTER, DROP), de conectarea (deconectarea) unui utilizator. Ei sunt definii la nivel de schem sau la nivel de baz de date. Declanatorii asociai unui tabel (stocai n baza de date) vor aciona indiferent de aplicaia care a efectuat operaia LMD. Dac operaia LMD se refer la o vizualizare, declanatorul INSTEAD OF definete aciunile care vor avea loc, iar dac aceste aciuni includ comenzi LMD referitoare la tabele, atunci declanatorii asociai acestor tabele sunt i ei, la rndul lor, activai. Dac declanatorii sunt asociai unei baze de date, ei se declaneaz pentru fiecare eveniment, pentru toi utilizatorii. Dac declanatorii sunt asociai unei scheme sau unui tabel, ei se declaneaz numai dac evenimentul declanator implic acea schem sau acel tabel. Un declanator se poate referi la un singur tabel sau la o singur vizualizare.

Programare n PL/SQL

349

Declanatorii aplicaie se execut implicit ori de cte ori apare un eveniment particular ntr-o aplicaie. De exemplu, o aplicaie dezvoltat cu Developer Suite. Form Builder utilizeaz frecvent acest tip de declanatori (form builder triggers). Ei pot fi declanai prin apsarea unui buton, prin navigarea pe un cmp etc. n acest capitol se va face referin doar la declanatorii baz de date. Atunci cnd un pachet sau un subprogram este depus n dicionarul datelor, alturi de codul surs este depus i p-codul compilat. n mod similar se ntmpl i pentru declanatori. Prin urmare, un declanator poate fi apelat fr recompilare. Declanatorii pot fi invalidai n aceeeai manier ca pachetele i subprogramele. Dac declanatorul este invalidat, el va fi recompilat la urmtoarea activare.

Crearea declanatorilor LMD


Declanatorii LMD sunt creai folosind comanda CREATE TRIGGER care are urmtoarea sintax general: CREATE [OR REPLACE] TRIGGER [schema.]nume_declanator {BEFORE | AFTER } {DELETE | INSERT | UPDATE [OF coloana[, coloana ] ] } [OR {DELETE | INSERT | UPDATE [OF coloana[, coloana ] ] } ON [schema.]nume_tabel [REFERENCING {OLD [AS] vechi NEW [AS] nou | NEW [AS] nou OLD [AS] vechi } ] [FOR EACH ROW] [WHEN (condiie) ] corp_declanator (bloc PL/SQL sau apelul unei proceduri); Numele declanatorului trebuie s fie unic printre numele declanatorilor din cadrul aceleai scheme, dar poate s coincid cu numele altor obiecte ale acesteia (de exemplu, tabele, vizualizri sau proceduri). La crearea unui declanator este obligatorie una dintre opiunile BEFORE sau AFTER, prin care se precizeaz momentul n care este executat corpul declanatorului. Acesta nu poate depi 32KB. Pn la versiunea Oracle8i, corpul unui declanator trebuia s fie un bloc PL/SQL. n ultimele versiuni, corpul poate consta doar dintr-o singur comand CALL. Procedura apelat poate fi un subprogram PL/SQL stocat, o rutin C sau o metod Java. n acest caz, CALL nu poate conine clauza INTO care este specific funciilor, iar pentru a referi coloanele tabelului asociat declanatorului, acestea trebuie prefixate de atributele :NEW sau :OLD. De asemenea, n expresia parametrilor nu pot s apar variabile bind. Declararea unui declanator trebuie s cuprind tipul comenzii SQL care duce la executarea declanatorului i tabelul asociat acestuia. n ceea ce privete tipul comenzii SQL care va duce la executarea declaatorului, sunt incluse urmtoarele tipuri de opiuni: DELETE, INSERT, UPDATE sau o combinare a acestora cu operatorul logic OR. Cel puin una dintre aceste opiuni este obligatorie. n declararea declanatorului este specificat tabelul asupra cruia va fi

350

PROGRAMARE AVANSAT N ORACLE9i

executat declanatorul. Oracle9i admite tablouri imbricate. Dac declanatorul este de tip UPDATE, atunci pot fi enumerate coloanele pentru care acesta se va executa. n corpul fiecrui declanator pot fi cunoscute valorile coloanelor att nainte de modificarea unei linii, ct i dup modificarea acesteia. Valoarea unei coloane nainte de modificare este referit prin atributul OLD, iar dup modificare, prin atributul NEW. Prin intermediul clauzei opionale REFERENCING din sintaxa comenzii de creare a declanatorilor, atributele NEW i OLD pot fi redenumite. Un declanator poate activa alt declanator, iar acesta la rndul su poate activa alt declanator etc. Aceast situaie (declanatori n cascad) poate avea ns efecte imprevizibile. Sistemul Oracle permite maximum 32 declanatori n cascad. Numrul acestora poate fi limitat (utiliznd parametrul de iniializare OPEN_CURSORS), deoarece pentru fiecare execuie a unui declanator trebuie deschis un nou cursor. Declanatorii la nivel de baze de date pot fi de dou feluri: la nivel de instruciune (statement level trigger); la nivel de linie (row level trigger).

Declanatori la nivel de instruciune


Declanatorii la nivel instruciune sunt executai o singur dat pentru instruciunea declanatoare, indiferent de numrul de linii afectate (chiar dac nici o linie nu este afectat). Un declanator la nivel de instruciune este util dac aciunea declanatorului nu depinde de informaiile din liniile afectate. Exemplu: Programul de lucru la administraia muzeului este de luni pn vineri, n intervalul (8:00 a.m. - 10:00 p.m.). S se construiasc un declanator la nivel de instruciune care mpiedic orice activitate asupra unui tabel al bazei de date, n afara acestui program.
CREATE OR REPLACE PROCEDURE verifica IS BEGIN IF ((TO_CHAR(SYSDATE,'D') BETWEEN 2 AND 6) AND TO_DATE(TO_CHAR(SYSDATE,'hh24:mi'), 'hh24:mi') NOT BETWEEN TO_DATE('08:00','hh24:mi') AND TO_DATE('22:00','hh24:mi')) THEN RAISE_APPLICATION_ERROR (-27733, 'nu puteti reactualiza acest tabel deoarece sunteti in afara programului'); END verifica; / CREATE OR REPLACE TRIGGER BIUD_tabel1 BEFORE INSERT OR UPDATE OR DELETE ON tabel1 BEGIN verifica; END;

Programare n PL/SQL /

351

Declanatori la nivel de linie


Declanatorii la nivel de linie sunt creai cu opiunea FOR EACH ROW. n acest caz, declanatorul este executat pentru fiecare linie din tabelul afectat, iar dac evenimentul declanator nu afecteaz nici o linie, atunci declanatorul nu este executat. Dac opiunea FOR EACH ROW nu este inclus, declanatorul este considerat implicit la nivel de instruciune. Declanatorii la nivel linie nu sunt performani dac se fac frecvent reactualizri pe tabele foarte mari. Restriciile declanatorilor pot fi incluse prin specificarea unei expresii booleene n clauza WHEN. Acest expresie este evaluat pentru fiecare linie afectat de ctre declanator. Declanatorul este executat pentru o linie, doar dac expresia este adevrat pentru acea linie. Clauza WHEN este valid doar pentru declanatori la nivel de linie. Exemplu: S se implementeze cu ajutorul unui declanator constrngerea c valorile operelor de art nu pot fi reduse (trei variante). Varianta 1:
CREATE OR REPLACE TRIGGER verifica_valoare BEFORE UPDATE OF valoare ON opera FOR EACH ROW WHEN (NEW.valoare < OLD.valoare) BEGIN RAISE_APPLICATION_ERROR (-20222, 'valoarea unei opere de arta nu poate fi micsorata'); END;

Varianta 2:
CREATE OR REPLACE TRIGGER verifica_valoare BEFORE UPDATE OF valoare ON opera FOR EACH ROW BEGIN IF (:NEW.valoare < :OLD.valoare) THEN RAISE_APPLICATION_ERROR (-20222, 'valoarea unei opere de arta nu poate fi micsorata'); END IF; END;

Varianta 3:
CREATE OR REPLACE TRIGGER verifica_valoare BEFORE UPDATE OF valoare ON opera FOR EACH ROW WHEN (NEW.valoare < OLD.valoare) CALL procedura -- care va face actiunea RAISE /

352

PROGRAMARE AVANSAT N ORACLE9i

Accesul la vechile i noile valori ale coloanelor liniei curente, afectat de evenimentul declanator, se face prin: OLD.nume_coloan (vechea valoare), respectiv prin NEW.nume_coloan (noua valoare). n cazul celor trei comenzi LMD, aceste valori devin: INSERT UPDATE DELETE : NEW.nume_coloan noua valoare (: OLD.nume_coloan NULL ); : NEW. nume_coloan noua valoare : OLD.nume_coloan vechea valoare; (: NEW.nume_coloan NULL ) : OLD.nume_coloan vechea valoare.

Exemplu: Se presupune c pentru fiecare galerie exist dou cmpuri ( min_valoare i max_valoare) n care se rein limitele minime i maxime ale valorile operelor din galeria respectiv. S se implementeze cu ajutorul unui declanator constrngerea c, dac aceste limite s-ar modifica, orice oper de art trebuie s aib valoarea cuprins ntre noile limite.
CREATE OR REPLACE TRIGGER verifica_limite BEFORE UPDATE OF min_valoare, max_valoare ON galerie FOR EACH ROW DECLARE v_min_val opera.valoare%TYPE; v_max_val opera.valoare%TYPE; e_invalid EXCEPTION; BEGIN SELECT MIN(valoare), MAX(valoare) INTO v_min_val, v_max_val FROM opera WHERE cod_galerie = :NEW.cod_galerie; IF (v_min_val < :NEW.min_valoare) OR (v_max_val > :NEW.max_valoare) THEN RAISE e_invalid; END IF; EXCEPTION WHEN e_invalid THEN RAISE_APPLICATION_ERROR (-20567, 'Exista opere de arta ale caror valori sunt in afara domeniului permis'); END verifica_limite; /

Ordinea de execuie a declanatorilor


PL/SQL permite definirea a 12 tipuri de declanatori care sunt obinui prin combinarea proprietii de moment (timp) al declanrii (BEFORE, AFTER), cu proprietatea nivelului la care acioneaz (nivel linie, nivel intruciune) i cu tipul operaiei ataate declanatorului (INSERT, UPDATE, DELETE). De exemplu, BEFORE INSERT acioneaz o singur dat, naintea

Programare n PL/SQL

353

executrii unei instruciuni INSERT, iar BEFORE INSERT FOR EACH ROW acioneaz nainte de inserarea fiecrei noi nregistrri. Declanatorii sunt activai cnd este executat o comand LMD. La apariia unei astfel de comenzi se execut cteva aciuni care vor fi descrise n continuare.

1) Se execut declanatorii la nivel de instruciune BEFORE. 2) Pentru fiecare linie afectat de comanda LMD: 2.1) se execut declanatorii la nivel de linie BEFORE; 2.2) se blocheaz i se modific linia afectat (se execut comanda

LMD), se verific constrngerile de integritate (blocarea rmne valabil pn n momentul n care tranzacia este permanentizat); 2.3) se execut declanatorii la nivel de linie AFTER. 3) Se execut declanatorii la nivel de instruciune AFTER. ncepnd cu versiunea Oracle8i algoritmul anterior se schimb, n sensul c verificarea constrngerii refereniale este amnat dup executarea declanatorului la nivel linie. Obsevaii:

n expresia clauzei WHEN nu pot fi incluse funcii definite de utilizator sau subcereri SQL. n clauza ON poate fi specificat un singur tabel sau o singur vizualizare. n interiorul blocului PL/SQL, coloanele tabelului prefixate cu OLD sau NEW sunt considerate variabile externe i deci, trebuie precedate de caracterul :. Condiia de la clauza WHEN poate conine coloane prefixate cu OLD sau NEW, dar n acest caz, acestea nu trebuie precedate de :. Declanatorii baz de date pot fi definii numai pe tabele (excepie, declanatorul INSTEAD OF care este definit pe o vizualizare). Totui, dac o comand LMD este aplicat unei vizualizri, pot fi activai declanatorii asociai tabelelor care definesc vizualizarea. Corpul unui declanator nu poate conine o interogare sau o reactualizare a unui tabel aflat n plin proces de modificare, pe timpul aciunii declanatorului (mutating table). Blocul PL/SQL care descrie aciunea declanatorului nu poate conine comenzi pentru gestiunea tranzaciilor (COMMIT, ROLLBACK, SAVEPOINT). Controlul tranzaciilor este permis, ns, n procedurile stocate. Dac un declanator apeleaz o procedur stocat care execut o comand referitoare la controlul tranzaciilor, atunci va aprea o eroare la execuie i tranzacia va fi anulat. Comenzile LDD nu pot s apar dect n declanatorii sistem. Corpul declanatorului poate s conin comenzi LMD. n corpul declanatorului pot fi referite i utilizate coloane LOB, dar nu pot fi modificate valorile acestora.

354

PROGRAMARE AVANSAT N ORACLE9i

n corpul declanatorului se pot insera date n coloanele de tip LONG i LONGRAW, dar nu pot fi declarate variabile de acest tip. Dac un tabel este suprimat (se terge din dicionarul datelor), automat sunt distrui toi declanatorii asociai tabelului. Nu este indicat crearea declanatorilor recursivi. Este necesar limitarea dimensiunii unui declanator. Dac acesta solicit mai mult de 60 linii de cod, atunci este preferabil ca o parte din cod s fie inclus ntr-o procedur stocat i aceasta s fie apelat din corpul declanatorului.

Sunt dou diferene eseniale ntre declanatori i procedurile stocate: declanatorii se invoc implicit, iar procedurile explicit; instruciunile LCD (COMMIT, ROLLBACK, SAVEPOINT) nu sunt permise n corpul unui declanator.

Predicate condiionale
n interiorul unui declanator care poate fi executat pentru diferite tipuri de instruciuni LMD se pot folosi trei funcii booleene prin care se stabilete tipul operaiei executate. Aceste predicate condiionale (furnizate de pachetul standard DBMS_STANDARD) sunt INSERTING, UPDATING i DELETING. Funciile booleene nu solicit prefixarea cu numele pachetului i determin tipul operaiei (INSERT, DELETE, UPDATE). De exemplu, predicatul INSERTING ia valoarea TRUE dac instruciunea declanatoare este INSERT. Similar sunt definite predicatele UPDATING i DELETING. Utiliznd aceste predicate, n corpul declanatorului se pot executa secvene de instruciuni diferite, n funcie de tipul operaiei LMD. n cazul n care corpul declanatorului este un bloc PL/SQL complet (nu o comand CALL), pot fi utilizate att predicatele INSERTING, UPDATING, DELETING, ct i identificatorii :OLD, :NEW, :PARENT. Exemplu: Se presupune c n tabelul galerie se pstreaz (ntr-o coloan numit total_val) valoarea total a operelor de art expuse n galeria respectiv.
UPDATE galerie SET total_val = (SELECT FROM WHERE

SUM(valoare) opera opera.cod_galerie = galerie.cod_galerie);

Reactualizarea acestui cmp poate fi implementat cu ajutorul unui declanator n urmtoarea manier:
CREATE OR REPLACE PROCEDURE creste (v_cod_galerie IN galerie.cod_galerie%TYPE, v_val IN galerie.total_val%TYPE) AS BEGIN UPDATE galerie

Programare n PL/SQL SET total_val = NVL (total_val, 0) + v_val WHERE cod_galerie = v_cod_galerie; END creste; /

355

CREATE OR REPLACE TRIGGER calcul_val AFTER INSERT OR DELETE OR UPDATE OF valoare ON opera FOR EACH ROW BEGIN IF DELETING THEN creste (:OLD.cod_galerie, -1*:OLD.valoare); ELSIF UPDATING THEN creste (:NEW.cod_galerie, :NEW.valoare - :OLD.valoare); ELSE /* inserting */ creste (:NEW.cod_galerie, :NEW.valoare); END IF; END; /

Declanatori INSTEAD OF
PL/SQL permite definirea unui nou tip de declanator, numit INSTEAD OF, care ofer o modalitate de actualizare a vizualizrilor obiect i a celor relaionale. Sintaxa acestui tip de declanator este similar celei pentru declanatori LMD, cu dou excepii: clauza {BEFORE | AFTER} este nlocuit prin INSTEAD OF; clauza ON [schema.]nume_tabel este nlocuit printr-una din clauzele ON [schema.]nume_view sau ON NESTED TABLE (nume_coloan) OF [schema.]nume_view. Declanatorul INSTEAD OF permite reactualizarea unei vizualizri prin comenzi LMD. O astfel de modificare nu poate fi realizat n alt manier, din cauza regulilor stricte existente pentru reactualizarea vizualizrilor. Declanatorii de tip INSTEAD OF sunt necesari, deoarece vizualizarea pe care este definit declanatorul poate, de exemplu, s se refere la join-ul unor tabele, i n acest caz, nu sunt actualizabile toate legturile. O vizualizare nu poate fi modificat prin comenzi LMD dac vizualizarea conine operatori pe mulimi, funcii grup, clauzele GROUP BY, CONNECT BY, START WITH, operatorul DISTINCT sau join-uri. Declanatorul INSTEAD OF este utilizat pentru a executa operaii LMD direct pe tabelele de baz ale vizualizrii. De fapt, se scriu comenzi LMD relative la o vizualizare, iar declanatorul, n locul operaiei originale, va opera pe tabelele de baz. De asemenea, acest tip de declanator poate fi definit asupra vizualizrilor ce au drept cmpuri tablouri imbricate, declanatorul furniznd o modalitate de reactualizare a elementelor tabloului imbricat. n acest caz, el se declaneaz doar n cazul n care comenzile LMD opereaz asupra tabloului imbricat (numai cnd elementele tabloului imbricat sunt modificate folosind clauzele THE() sau TABLE())

356

PROGRAMARE AVANSAT N ORACLE9i

i nu atunci cnd comanda LMD opereaz doar asupra vizualizrii. Declanatorul permite accesarea liniei printe ce conine tabloul imbricat modificat. Observaii:

Spre deosebire de declanatorii BEFORE sau AFTER, declanatorii INSTEAD OF se execut n locul instruciunii LMD (INSERT, UPDATE, DELETE) specificate. Opiunea UPDATE OF nu este permis pentru acest tip de declanator. Declanatorii INSTEAD OF se definesc pentru o vizualizare, nu pentru un tabel. Declanatorii INSTEAD OF acioneaz implicit la nivel de linie. Dac declanatorul este definit pentru tablouri imbricate, atributele :OLD i :NEW se refer la liniile tabloului imbricat, iar pentru a referi linia curent din tabloul printe s-a introdus atributul :PARENT.

Exemplu: Se consider nou_opera, respectiv nou_artist, copii ale tabelelor opera, respectiv artist i vi_op_ar o vizualizare definit prin compunerea natural a celor dou tabele. Se presupune c pentru fiecare artist exist un cmp (sum_val) ce reprezint valoarea total a operelor de art expuse de acesta n muzeu. S se defineasc un declanator prin care reactualizrile executate asupra vizualizrii vi_op_ar se vor transmite automat tabelelor nou_opera i nou_artist.
CREATE TABLE nou_opera AS SELECT cod_opera, cod_artist, valoare, tip FROM opera; CREATE TABLE nou_artist AS SELECT cod_artist, nume, sum_val FROM artist; CREATE VIEW vi_op_ar AS SELECT cod_opera, o.cod_artist, valoare, tip, nume, sum_val FROM opera o, artist a WHERE o.cod_artist = a.cod_artist CREATE OR REPLACE TRIGGER react INSTEAD OF INSERT OR DELETE OR UPDATE ON vi_op_ar FOR EACH ROW BEGIN IF INSERTING THEN INSERT INTO nou_opera VALUES (:NEW.cod_opera, :NEW.cod_artist, :NEW.valoare, :NEW.tip); UPDATE nou_artist SET sum_val = sum_val + :NEW.valoare WHERE cod_artist = :NEW.cod_artist; ELSIF DELETING THEN DELETE FROM nou_opera WHERE cod_opera = :OLD.cod_opera;

Programare n PL/SQL

357

UPDATE nou_artist SET sum_val = sum_val - :OLD.valoare WHERE cod_artist = :OLD.cod_artist; ELSIF UPDATING ('valoare') THEN UPDATE nou_opera SET valoare = :NEW.valoare WHERE cod_opera = :OLD.cod_opera; UPDATE nou_artist SET sum_val = sum_val + (:NEW.valoare - :OLD.valoare) WHERE cod_artist = :OLD.cod_artist; ELSIF UPDATING ('cod_artist') THEN UPDATE nou_opera SET cod_artist = :NEW.cod_artist WHERE cod_opera = :OLD.cod_opera; UPDATE nou_artist SET sum_val = sum_val - :OLD.valoare WHERE cod_artist = :OLD.cod_artist; UPDATE nou_artist SET sum_val = sum_val + :NEW.valoare WHERE cod_artist = :NEW.cod_artist; END IF; END; /

Declanatori sistem
Declanatorii sistem sunt activai de comenzi LDD (CREATE, DROP, ALTER) i de anumite evenimente sistem (STARTUP, SHUTDOWN , LOGON, LOGOFF, SERVERERROR, SUSPEND). Un declanator sistem poate fi definit la nivelul bazei de date sau la nivelul schemei. Sintaxa pentru crearea unui astfel de declanator este urmtoarea: CREATE [OR REPLACE] TRIGGER [schema.]nume_declanator {BEFORE | AFTER } {lista_evenimente_LDD | lista_evenimente_baz} ON {DATABASE | SCHEMA } [WHEN (condiie) ] corp_declanator; Cuvintele cheie DATABASE sau SCHEMA specific nivelul declanatorului. Exist restricii asupra expresiilor din condiia clauzei WHEN. De exemplu, declanatorii LOGON i LOGOFF pot verifica doar identificatorul (userid) i numele utilizatorului (username), iar declanatorii LDD pot verifica tipul i numele obiectelor definite, identificatorul i numele utilizatorului. Evenimentele amintite anterior pot fi asociate clauzelor BEFORE sau AFTER. De exemplu, un declanator LOGON (AFTER) se activeaz dup ce un utilizator s-a conectat la baza de date, un declanator CREATE (BEFORE sau AFTER) se activeaz nainte sau dup ce a fost creat un obiect al bazei, un

358

PROGRAMARE AVANSAT N ORACLE9i

declanator SERVERERROR (AFTER) se activeaz ori de cte ori apare o eroare (cu excepia erorilor: ORA-01403, ORA-01422, ORA-01423, ORA-01034, ORA04030). Declanatorii LDD se activeaz numai dac obiectul creat este de tip table, cluster, function, index, package, role, sequence, synonym, tablespace, trigger, type, view sau user. Pentru declanatorii sistem se pot utiliza funcii speciale care permit obinerea de informaii referitoare la evenimentul declanator. Ele sunt funcii PL/SQL stocate care trebuie prefixate de numele proprietarului (SYS). Printre cele mai importante funcii care furnizeaz informaii referitoare la evenimentul declanator, se remarc: SYSEVENT returneaz evenimentul sistem care a activat declanatorul (este de tip VARCHAR2(20) i este aplicabil oricrui eveniment); DATABASE_NAME returneaz numele bazei de date curente (este de tip VARCHAR2(50) i este aplicabil oricrui eveniment); SERVER_ERROR returneaz codul erorii a crei poziie n stiva erorilor este dat de argumentul de tip NUMBER al funciei (este de tip NUMBER i este aplicabil evenimentului SERVERERROR); LOGIN_USER returneaz identificatorul utilizatorului care activeaz declanatorul (este de tip VARCHAR2(30) i este aplicabil oricrui eveniment); DICTIONARY_OBJ_NAME returneaz numele obiectului la care face referin comanda LDD ce a activat declanatorul (este de tip VARCHAR2(30) i este aplicabil evenimentelor CREATE, ALTER, DROP). Exemplu:
CREATE OR REPLACE TRIGGER logutiliz AFTER CREATE ON SCHEMA BEGIN INSERT INTO ldd_tab(user_id, object_name, creation_date) VALUES (USER, SYS.DICTIONARY_OBJ_NAME, SYSDATE); END logutiliz;

Evenimentul SERVERERROR poate fi utilizat pentru a urmri erorile care apar n baza de date. Codul erorii este furnizat, prin intermediul declanatorului, de funcia SERVER_ERROR, iar mesajul asociat erorii poate fi obinut cu procedura DBMS_UTILITY.FORMAT_ERROR_STACK. Exemplu:
CREATE TABLE erori ( moment DATE, utilizator VARCHAR2(30), nume_baza VARCHAR2(50), stiva_erori VARCHAR2(2000) ); /

Programare n PL/SQL CREATE OR REPLACE TRIGGER logerori AFTER SERVERERROR ON DATABASE BEGIN INSERT INTO erori VALUES (SYSDATE, SYS.LOGIN_USER, SYS.DATABASE_NAME, DBMS_UTILITY.FORMAT_ERROR_STACK); END logerori; /

359

Modificarea i suprimarea declanatorilor


Opiunea OR REPLACE din cadrul comenzii CREATE TRIGGER recreeaz declanatorul, dac acesta exist. Clauza permite schimbarea definiiei unui declanator existent fr suprimarea acestuia. Similar procedurilor i pachetelor, un declanator poate fi suprimat prin: DROP TRIGGER [schema.]nume_declanator; Uneori aciunea de suprimare a unui declanator este prea drastic i este preferabil doar dezactivarea sa temporar. n acest caz, declanatorul va continua s existe n dicionarul datelor. Modificarea unui declanator poate consta din recompilarea (COMPILE), redenumirea (RENAME), activarea (ENABLE) sau dezactivarea (DISABLE) acestuia i se realizeaz prin comanda: ALTER TRIGGER [schema.]nume declanator {ENABLE | DISABLE | COMPILE | RENAME TO nume_nou} {ALL TRIGGERS } Dac un declanator este activat, atunci sistemul Oracle l execut ori de cte ori au loc operaiile precizate n declanator asupra tabelului asociat i cnd condiia de restricie este ndeplinit. Dac declanatorul este dezactivat, atunci sistemul Oracle nu l va mai executa. Dup cum s-a mai subliniat, dezactivarea unui declanator nu implic tergerea acestuia din dicionarul datelor. Toi declanatorii asociai unui tabel pot fi activai sau dezactivai utiliznd opiunea ALL TRIGGERS (ENABLE ALL TRIGGERS, respectiv DISABLE ALL TRIGGERS). Declanatorii sunt activai n mod implicit atunci cnd sunt creai. Activarea i dezactivarea declanatorilor asociai unui tabel se poate realiza i cu ajutorul comenzii ALTER TABLE. Un declanator este compilat n mod automat la creare. Dac un site este neutilizabil atunci cnd declanatorul trebuie compilat, sistemul Oracle nu poate valida comanda de accesare a bazei distante i compilarea eueaz.

Informaii despre declanatori


n dicionarul datelor exist vizualizri ce conin informaii despre declanatori i despre starea acestora (USER_TRIGGERS, USER_TRIGGER_COL, ALL_TRIGGERS, DBA_TRIGGERS etc.). Aceste vizualizri sunt actualizate ori de cte ori un declanator este creat sau suprimat.

360

PROGRAMARE AVANSAT N ORACLE9i

Atunci cnd declanatorul este creat, codul su surs este stocat n vizualizarea USER_TRIGGERS. Vizualizarea ALL_TRIGGERS conine informaii despre toi declanatorii din baza de date. Pentru a detecta dependenele declanatorilor poate fi consultat vizualizarea USER_DEPENDENCIES, iar ALL_DEPENDECIES conine informaii despre dependenele tuturor obiectelor din baza de date. Erorile rezultate din compilarea declanatorilor pot fi analizate din vizualizarea USER_ERRORS, iar prin comanda SHOW ERRORS se vor afia erorile corespunztoare ultimului declanator compilat. n operaiile de gestiune a bazei de date este necesar uneori reconstruirea instruciunilor CREATE TRIGGER, atunci cnd codul surs original nu mai este disponibil. Aceasta se poate realiza utiliznd vizualizarea USER_TRIGGERS. Vizualizarea include numele declanatorului (TRIGGER_NAME), tipul acestuia (TRIGGER_TYPE), evenimentul declanator (TRIGGERING_EVENT), numele proprietarului tabelului (TABLE_OWNER), numele tabelului pe care este definit declanatorul (TABLE_NAME), clauza WHEN (WHEN_CLAUSE), corpul declanatorului (TRIGGER_BODY), antetul (DESCRIPTION), starea acestuia (STATUS) care poate s fie ENABLED sau DISABLED i numele utilizate pentru a referi parametrii OLD i NEW (REFERENCING_NAMES). Dac obiectul de baz nu este un tabel sau o vizualizare, atunci TABLE_NAME este null. Exemplu: Presupunnd c nu este disponibil codul surs pentru declanatorul alfa, s se reconstruiasc instruciunea CREATE TRIGGER corespunztoare acestuia.
SELECT FROM WHERE CREATE OR REPLACE TRIGGER || DESCRIPTION || TRIGGER_BODY USER_TRIGGERS TRIGGER_NAME = 'ALFA';

Cu aceast interogare se pot reconstrui numai declanatorii care aparin contului utilizator curent. O interogare a vizualizrilor ALL_TRIGGERS sau DBA_TRIGGERS permite reconstruirea tuturor declanatorilor din sistem, dac se dispune de privilegii DBA.

Privilegii sistem
Sistemul furnizeaz urmtoarele privilegii sistem pentru gestiunea declanatorilor: CREATE TRIGGER (permite crearea declanatorilor n schema personal); CREATE ANY TRIGGER (permite crearea declanatorilor n orice schem cu excepia celei corespunztoare lui SYS); ALTER ANY TRIGER (permite activarea, dezactivarea sau compilarea declanatorilor n orice schem cu excepia lui SYS); DROP ANY TRIGGER (permite suprimarea declanatorilor la nivel de baz de date n orice schem cu excepia celei corespunztoate lui SYS); ADMINISTER DATABASE TRIGGER (permite crearea sau modificarea unui declanator sistem referitor la baza de date); EXECUTE (permite referirea, n corpul declanatorului, a procedurilor,

Programare n PL/SQL

361

funciilor sau pachetelor din alte scheme).

Tabele mutating
Asupra tabelelor i coloanelor care pot fi accesate de corpul declanatorului exist anumite restricii. Pentru a analiza aceste restricii este necesar definirea tabelelor n schimbare (mutating) i constrnse (constraining). Un tabel constraining este un tabel pe care evenimentul declanator trebuie s-l consulte fie direct, printr-o instruciune SQL, fie indirect, printr-o constrngere de integritate referenial declarat. Tabelele nu sunt considerate constraining n cazul declanatorilor la nivel de instruciune. Un tabel mutating este tabelul modificat de instruciunea UPDATE, DELETE sau INSERT, sau un tabel care va fi actualizat prin efectele aciunii integritii refereniale ON DELETE CASCADE. Chiar tabelul pe care este definit declanatorul este un tabel mutating, ca i orice tabel referit printr-o constrngere FOREING KEY. Tabelele nu sunt considerate mutating pentru declanatorii la nivel de instruciune, iar vizualizrile nu sunt considerate mutating n declanatorii INSTEAD OF. Principalele reguli care trebuie respectate la utilizarea declanatoriilor sunt: comenzile SQL din corpul unui declanator nu pot consulta sau modifica valorile coloanelor care sunt declarate chei primare, externe sau unice (PRIMARY KEY, FOREIGN KEY, UNIQUE KEY ) ntr-un tabel constraining; comenzile SQL din corpul unui declanator nu pot consulta sau modifica date dintr-un tabel mutating. Dac o comand INSERT afecteaz numai o nregistrare, declanatorii la nivel de linie (BEFORE sau AFTER) pentru nregistrarea respectiv nu trateaz tabelul ca fiind mutating. Acesta este unicul caz n care un declanator la nivel de linie poate citi sau modifica tabelul. Comanda INSERT INTO tabel SELECT consider tabelul mutating chiar dac cererea returneaz o singur linie. Exemplu:
CREATE OR REPLACE TRIGGER cascada AFTER UPDATE OF cod_artist ON artist FOR EACH ROW BEGIN UPDATE opera SET opera.cod_artist = :NEW.cod_artist WHERE opera.cod_artist = :OLD.cod_artist END; / UPDATE artist SET cod_artist = 71 WHERE cod_artist = 23;

La execuia acestei secvene este semnalat o eroare. Tabelul artist

362

PROGRAMARE AVANSAT N ORACLE9i

refereniaz tabelul opera printr-o constrngere de cheie extern. Prin urmare, tabelul opera este constraining, iar declanatorul cascada ncearc s schimbe date n tabelul constraining, ceea ce nu este permis. Exemplul va funciona corect dac nu este definit sau activat constrngerea referenial ntre cele dou tabele. Exemplu: S se implementeze cu ajutorul unui declanator restricia c ntr-o sal pot s fie expuse maximum 10 opere de art.
CREATE OR REPLACE TRIGGER TrLimitaopere BEFORE INSERT ON opera FOR EACH ROW DECLARE v_Max_opere CONSTANT NUMBER := 10; v_opere_curente NUMBER; BEGIN SELECT COUNT(*) INTO v_opere_curente FROM opera WHERE cod_sala = :NEW.cod_sala; IF v_opere_curente + 1 > v_Max_opere THEN RAISE_APPLICATION_ERROR(-20000,'Prea multe opere de arta in sala avand codul ' || :NEW.cod_sala); END IF; END TrLimitaopere;

Cu toate c declanatorul pare s produc lucrul dorit, totui dup o reactualizare a tabelului opera n urmtoarea manier:
INSERT INTO opera (cod_opera, cod_sala) VALUES (756893, 10);

se obine urmtorul mesaj de eroare:


ORA-04091: tabel opera is mutating, trigger/function may not see it ORA-04088: error during execution of trigger TrLimitaopere

Eroarea ORA-04091 apare deorece declanatorul TrLimitaopere consult chiar tabelul (opera) la care este asociat declanatorul (mutating). Tabelul opera este mutating doar pentru un declanator la nivel de linie. Aceasta nseamn c tabelul poate fi consultat n interiorul unui declanator la nivel de instruciune. Totui, limitarea numrului operelor de art nu poate fi fcut n interiorul unui declanator la nivel de instruciune, din moment ce este necesar valoarea :NEW.cod_sala n corpul declanatorului. Soluia pentru acest problem este crearea a doi declanatori, unul la nivel de linie i altul la nivel de instruciune. n declanatorul la nivel de linie se nregistreaz valoarea lui :NEW.cod_sala, dar nu va fi interogat tabelul opera. Interogarea va fi fcut n declanatorul la nivel de instruciune i va folosi valoarea nregistrat n declanatorul la nivel de linie. O modalitate pentru a nregistra valoarea lui :NEW.cod_sala este utilizarea unui tablou indexat n interiorul unui pachet.

Programare n PL/SQL CREATE OR REPLACE PACKAGE PSalaDate AS TYPE t_cod IS TABLE OF sala.cod_sala%TYPE INDEX BY BINARY_INTEGER; v_cod_sala t_cod; v_NrIntrari BINARY_INTEGER := 0; END PSalaDate; CREATE OR REPLACE TRIGGER TrLLimitaOpere BEFORE INSERT ON sala FOR EACH ROW BEGIN PSalaDate.v_NrIntrari := PSalaDate.v_NrIntrari + 1; PSalaDate.v_cod_sala (PSalaDate.v_NrIntrari) := :NEW.cod_sala; END TrLLimitaTOpere;

363

CREATE OR REPLACE TRIGGER TrILimitaopere BEFORE INSERT ON opera DECLARE v_Max_opere CONSTANT NUMBER := 10; v_opere_curente NUMBER; v_cod_sala sala.cod_sala%TYPE; BEGIN /* Parcurge fiecare opera inserata sau actualizata si verifica daca se incadreaza in limita stabilita */ FOR v_LoopIndex IN 1..PsalaDate.v_NrIntrari LOOP v_cod_sala := PsalaDate.v_cod_sala(v_LoopIndex); SELECT COUNT(*) INTO v_opere_curente FROM opera WHERE cod_sala = v_cod_sala; IF v_opere_curente > v_Max_opere THEN RAISE_APPLICATION_ERROR(-20000, 'Prea multe opere de arta in sala avand codul: ' || v_cod_sala); END IF; END LOOP; /* Reseteaza contorul deoarece urmatoarea executie va folosi date noi */ PSalaDate.v_NrIntrari := 0; END TrILimitaopere;

Exemplu: S se creeze un declanator care: a) dac este eliminat o sal, va terge toate operele expuse n sala respectiv; b) dac se schimb codul unei sli, va modifica aceast valoare pentru fiecare oper de art expus n sala respectiv.
CREATE OR REPLACE TRIGGER sala_cascada BEFORE DELETE OR UPDATE OF cod_sala ON sala FOR EACH ROW

364

PROGRAMARE AVANSAT N ORACLE9i

BEGIN IF DELETING THEN DELETE FROM opera WHERE cod_sala = :OLD.cod_sala; END IF; IF UPDATING AND :OLD.cod_sala != :NEW.cod_sala THEN UPDATE opera SET cod_sala = :NEW.cod_sala WHERE cod_sala = :OLD.cod_sala; END IF; END sala_cascada;

Declanatorul anterior realizeaz constrngerea de integritate UPDATE sau ON DELETE CASCADE, adic tergerea sau modificarea cheii primare a unui tabel printe se va reflecta i asupra nregistrrilor corespunztoare din tabelul copil. Executarea acestuia, pe tabelul sala (tabelul printe), va duce la efectuarea a dou tipuri de operaii pe tabelul opera (tabelul copil). La eliminarea unei sli din tabelul sala, se vor terge toate operele de art corespunztoare acestei sli.
DELETE FROM sala WHERE cod_sala = 773;

La modificarea codului unei sli din tabelul sala, se va actualiza codul slii att n tabelul sala, ct i n tabelul opera.
UPDATE SET WHERE sala cod_sala = 777 cod_sala = 333;

Se presupune c asupra tabelului opera exist o constrngere de integritate:


FOREIGN KEY (cod_sala) REFERENCES sala(cod_sala)

n acest caz sistemul Oracle va afia un mesaj de eroare prin care se precizeaz c tabelul sala este mutating, iar constrngerea definit mai sus nu poate fi verificat.
ORA-04091: table MASTER.SALA is mutating, trigger/function may not see it

Pachetele pot fi folosite pentru ncapsularea detaliilor logice legate de declanatori. Exemplul urmtor arat un mod simplu de implementare a acestei posibiliti. Este permis apelarea unei proceduri sau funcii stocate din blocul PL/SQL care reprezint corpul declanatorului. Exemplu:
CREATE OR REPLACE PACKAGE pachet IS PROCEDURE procesare_trigger(pvaloare IN NUMBER, pstare IN VARCHAR2); END pachet; CREATE OR REPLACE PACKAGE BODY pachet IS

Programare n PL/SQL PROCEDURE procesare_trigger(pvaloare IN NUMBER, pstare IN VARCHAR2) IS BEGIN END procesare_trigger; END pachet; CREATE OR REPLACE TRIGGER gama AFTER INSERT ON opera FOR EACH ROW BEGIN pachet.procesare_trigger(:NEW.valoare,:NEW.stare) END;

365

10. Tratarea erorilor n PL/SQL


Mecanismul de gestiune a erorilor permite utilizatorului s defineasc i s controleze comportamentul programului atunci cnd acesta genereaz o eroare. n acest fel, aplicaia nu este oprit, revenind ntr-un regim normal de execuie. ntr-un program PL/SQL pot s apar erori la compilare sau erori la execuie. Erorile care apar n timpul compilrii sunt detectate de motorul PL/SQL i sunt comunicate programatorului care va face corecia acestora. Programul nu poate trata aceste erori deoarece nu a fost nc executat. Erorile care apar n timpul execuiei nu mai sunt tratate interactiv. n program trebuie prevzut apariia unei astfel de erori i specificat modul concret de tratare a acesteia. Atunci cnd apare eroarea este declanat o excepie, iar controlul trece la o seciune separat a programului, unde va avea loc tratarea erorii. Gestiunea erorilor n PL/SQL face referire la conceptul de excepie. Excepia este un eveniment particular (eroare sau avertisment) generat de server-ul Oracle sau de aplicaie, care necesit o tratare special. n PL/SQL mecanismul de tratare a excepiilor permite programului s i continue execuia i n prezena anumitor erori. Excepiile pot fi definite, activate, tratate la nivelul fiecrui bloc din program (program principal, funcii i proceduri, blocuri interioare acestora). Execuia unui bloc se termin ntotdeauna atunci cnd apare o excepie, dar se pot executa aciuni ulterioare apariiei acesteia, ntr-o seciune special de tratare a excepiilor. Posibilitatea de a da nume fiecrei excepii, de a izola tratarea erorilor ntr-o seciune particular, de a declana automat erori (n cazul excepiilor interne) mbuntete lizibilitatea i fiabilitatea programului. Prin utilizarea excepiilor i rutinelor de tratare a excepiilor, un program PL/SQL devine robust i capabil s trateze att erorile ateptate, ct i cele neateptate ce pot aprea n timpul execuiei.

Seciunea de tratare a erorilor

366

PROGRAMARE AVANSAT N ORACLE9i

Pentru a gestiona excepiile, utilizatorul trebuie s scrie cteva comenzi care preiau controlul derulrii blocului PL/SQL. Aceste comenzi sunt situate n seciunea de tratare a erorilor dintr-un bloc PL/SQL i sunt cuprinse ntre cuvintele cheie EXCEPTION i END, conform urmtoarei sintaxe generale: EXCEPTION WHEN nume_excepie1 [OR nume_excepie2 ] THEN secvena_de_instruciuni_1; [WHEN nume_excepie3 [OR nume_excepie4 ] THEN secvena_de_instruciuni_2;] [WHEN OTHERS THEN secvena_de_instruciuni_n;] END; De remarcat c WHEN OTHERS trebuie s fie ultima clauz i trebuie s fie unic. Toate excepiile care nu au fost analizate vor fi tratate prin aceast clauz. Evident, n practic nu se utilizeaz forma WHEN OTHERS THEN NULL. n PL/SQL exist dou tipuri de excepii: excepii interne, care se produc atunci cnd un bloc PL/SQL nu respect o regul Oracle sau depete o limit a sistemului de operare; excepii externe definite de utilizator (user-defined error), care sunt declarate n seciunea declarativ a unui bloc, subprogram sau pachet i care sunt activate explicit n partea executabil a blocului PL/SQL. Excepiile interne PL/SQL sunt de dou tipuri: excepii interne predefinite (predefined Oracle Server error); excepii interne nepredefinite (non-predefined Oracle Server error).

Funcii pentru identificarea excepiilor


Indiferent de tipul excepiei, aceasta are asociate dou elemente: un cod care o identific; un mesaj cu ajutorul cruia se poate interpreta excepia respectiv. Cu ajutorul funciilor SQLCODE i SQLERRM se pot obine codul i mesajul asociate excepiei declanate. Lungimea maxim a mesajului este de 512 caractere. De exemplu, pentru eroarea predefinit ZERO_DIVIDE, codul SQLCODE asociat este -1476, iar mesajul corespunztor erorii, furnizat de SQLERRM, este divide by zero error. Codul erorii este: un numr negativ, n cazul unei erori sistem; numrul +100, n cazul excepiei NO_DATA_FOUND; numrul 0, n cazul unei execuii normale (fr excepii); numrul 1, n cazul unei excepii definite de utilizator. Funciile SQLCODE i SQLERRM nu se pot utiliza direct ca parte a unei

Programare n PL/SQL

367

instruciuni SQL. Valorile acestora trebuie atribuite unor variabile locale. Rezultatul funciei SQLCODE poate fi asignat unei variabile de tip numeric, iar cel al funciei SQLERRM unei variabile de tip caracter. Variabilele locale astfel definite pot fi utilizate n comenzi SQL. Exemplu: S se scrie un bloc PL/SQL prin care s se exemplifice situaia comentat.
DECLARE eroare_cod NUMBER; eroare_mesaj VARCHAR2(100); BEGIN EXCEPTION WHEN OTHERS THEN eroare_cod := SQLCODE; eroare_mesaj := SUBSTR(SQLERRM,1,100); INSERT INTO erori VALUES (eroare_cod, eroare_mesaj); END;

Mesajul asociat excepiei declanate poate fi furnizat i de funcia DBMS_UTILITY.FORMAT_ERROR_STACK.

Excepii interne
Excepiile interne se produc atunci cnd un bloc PL/SQL nu respect o regul Oracle sau depete o limit a sistemului de exploatare. Aceste excepii pot fi independente de structura bazei de date sau pot s apar datorit nerespectrii constrngerilor statice implementate n structur (PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE, CHECK). Atunci cnd apare o eroare Oracle, excepia asociat ei se declaneaz implicit. De exemplu, dac apare eroarea ORA-01403 (deoarece o comand SELECT nu returneaz nici o linie), atunci implicit PL/SQL activeaz excepia NO_DATA_FOUND. Cu toate c fiecare astfel de excepie are asociat un cod specific, ele trebuie referite prin nume.

Excepii interne predefinite


Excepiile interne predefinite nu trebuie declarate n seciunea declarativ i sunt tratate implicit de ctre server-ul Oracle. Ele sunt referite prin nume (CURSOR_ALREADY_OPEN, DUP_VAL_ON_INDEX, NO_DATA_FOUND etc.). PL/SQL declar aceste excepii n pachetul DBMS_STANDARD. Excepii predefinite Nume excepie
ACCES_INTO_NULL

Cod eroare Descriere Oracle


ORA-06530 Asignare de valori atributelor unui obiect neiniializat.

368
CASE_NOT_FOUND

PROGRAMARE AVANSAT N ORACLE9i ORA-06592 Nu este selectat nici una din clauzele WHEN ale lui CASE i nu exist nici clauza ELSE (excepie specific lui Oracle9i ). Aplicarea unei metode (diferite de EXISTS) unui tabel imbricat sau unui vector neiniializat. Deschiderea unui cursor care este deja dechis. Detectarea unei dubluri ntr-o coloan unde acestea sunt interzise. Operaie ilegal asupra unui cursor. Conversie nepermis de la tipul ir de caractere la numr. Nume sau parol incorecte. Comanda SELECT nu returneaz nici o nregistrare. Programul PL/SQL apeleaz baza fr s fie conectat la Oracle. Apelul unei metode cnd instana este NULL. PL/SQL are o problem intern. Incompatibilitate ntre parametrii actuali i formali, la deschiderea unui cursor parametrizat. PL/SQL are probleme cu spaiul de memorie. Referire la o component a unui nested table sau varray, folosind un index mai mare dect numrul elementelor coleciei respective. Referire la o component a unui tabel imbricat sau vector, folosind un index care este n afara domeniului (de exemplu, -1). Conversia unui ir de caractere ntr-un ROWID nu se poate face deoarece irul nu reprezint un ROWID valid. Expirarea timpului de ateptare pentru eliberarea unei resurse. Tranzacia a fost anulat datorit unei interblocri. SELECTINTO ntoarce mai multe linii. Apariia unor erori n conversii, constrngeri sau erori aritmetice. Sesizarea unei mpriri la zero.

COLLECTION_IS_NULL CURSOR_ALREADY_OPEN DUP_VAL_ON_INDEX INVALID_CURSOR INVALID_NUMBER LOGIN_DENIED NO_DATA_FOUND NOT_LOGGED_ON SELF_IS_NULL PROGRAM_ERROR ROWTYPE_MISMATCH STORAGE_ERROR SUBSCRIPT_BEYOND_COUNT

ORA-06531 ORA-06511 ORA-00001 ORA-01001 ORA-01722 ORA-01017 ORA-01403 ORA-01012 ORA-30625 ORA-06501 ORA-06504 ORA-06500 ORA-06533

SUBSCRIPT_OUTSIDE_LIMIT

ORA-06532

SYS_INVALID_ROWID

ORA-01410

TIMEOUT_ON_RESOURCE TRANSACTION_BACKED_OUT TOO_MANY_ROWS VALUE_ERROR ZERO_DIVIDE

ORA-00051 ORA-00061 ORA-01422 ORA-06502 ORA-01476

Programare n PL/SQL

369

Exemplu: S se scrie un bloc PL/SQL prin care s se afieze numele artitilor de o anumit naionalitate care au opere de art expuse n muzeu. 1) Dac rezultatul interogrii returneaz mai mult dect o linie, atunci s se trateze excepia i s se insereze n tabelul mesaje textul mai muli creatori. 2) Dac rezultatul interogrii nu returneaz nici o linie, atunci s se trateze excepia i s se insereze n tabelul mesaje textul nici un creator. 3) Dac rezultatul interogrii este o singur linie, atunci s se insereze n tabelul mesaje numele artistului i pseudonimul acestuia. 4) S se trateze orice alt eroare, insernd n tabelul mesaje textul alte erori au aprut.
SET VERIFY OFF ACCEPT national PROMPT 'Introduceti nationalitatea:' DECLARE v_nume_artist artist.nume%TYPE; v_pseudonim artist.pseudonim%TYPE; v_national artist.national%TYPE:='&national'; BEGIN SELECT nume, pseudonim INTO v_nume_artist, v_pseudonim FROM artist WHERE national = v_national; INSERT INTO mesaje (rezultate) VALUES (v_nume_artist||'-'||v_pseudonim); EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO mesaje (rezultate) VALUES ('nici un creator'); WHEN TOO_MANY_ROWS THEN INSERT INTO mesaje (rezultate) VALUES ('mai multi creatori'); WHEN OTHERS THEN INSERT INTO mesaje (rezultate) VALUES ('alte erori au aparut'); END; / SET VERIFY ON

Aceeai excepie poate s apar n diferite circumstane. De exemplu, excepia NO_DATA_FOUND poate fi generat fie pentru c o interogare nu ntoarce un rezultat, fie pentru c se refer un element al unui tablou PL/SQL care nu a fost definit (nu are atribuit o valoare). Dac ntr-un bloc PL/SQL apar ambele situaii, este greu de stabilit care dintre ele a generat eroarea i este necesar restructurarea blocului, astfel nct acesta s poat diferenia cele dou situaii.

Excepii interne nepredefinite

370

PROGRAMARE AVANSAT N ORACLE9i

Excepiile interne nepredefinite sunt declarate n seciunea declarativ i sunt tratate implicit de ctre server-ul Oracle. Ele pot fi gestionate prin clauza OTHERS, n seciunea EXCEPTION. Diferenierea acestor erori este posibil doar cu ajutorul codului. Dup cum s-a mai specificat, codul unei excepii interne este un numr negativ, n afar de excepia NO_DATA_FOUND, care are codul +100. O alt metod pentru tratarea unei erori interne nepredefinite (diferit de folosirea clauzei OTHERS drept detector universal de excepii) este utilizarea directivei de compilare (pseudo-instruciune) PRAGMA EXCEPTION_INIT. Aceast directiv permite asocierea numelui unei excepii cu un cod de eroare intern. n felul acesta, orice excepie intern poate fi referit printr-un nume i se pot scrie rutine speciale pentru tratarea acesteia. Directiva este procesat n momentul compilrii, i nu la execuie. Directiva trebuie s apar n partea declarativ a unui bloc, pachet sau subprogram, dup definirea numelui excepiei. PRAGMA EXCEPTION_INIT poate s apar de mai multe ori ntr-un program. De asemenea, pot fi asignate mai multe nume pentru acelai cod de eroare. n acest caz, tratarea erorii se face n urmtoarea manier: 1) se declar numele excepiei n partea declarativ sub forma: nume_excepie EXCEPTION;
2) se asociaz numele excepiei cu un cod eroare standard Oracle,

utiliznd comanda: PRAGMA EXCEPTION_INIT (nume_excepie, cod_eroare);


3) se refer excepia n seciunea de gestiune a erorilor (excepia este

tratat automat, fr a fi necesar comanda RAISE). Exemplu: Dac exist opere de art create de un anumit artist, s se tipreasc un mesaj prin care utilizatorul este anunat c artistul respectiv nu poate fi ters din baza de date (violarea constrngerii de integritate avnd codul eroare Oracle -2292).
SET VERIFY OFF DEFINE p_nume = Monet DECLARE opera_exista EXCEPTION; PRAGMA EXCEPTION_INIT(opera_exista,-2292); BEGIN DELETE FROM artist WHERE nume = '&p_nume'; COMMIT; EXCEPTION WHEN opera_exista THEN DBMS_OUTPUT.PUT_LINE ('nu puteti sterge artistul cu numele ' || '&p_nume' || ' deoarece exista in muzeu opere de arta create de acesta'); END; /

Programare n PL/SQL SET VERIFY ON

371

Excepii externe
PL/SQL permite utilizatorului s defineasc propriile sale excepii. Aceste excepii pot s apar n toate seciunile unui bloc, subprogram sau pachet. Excepiile externe sunt definite n partea declarativ a blocului, deci posibilitatea de referire la ele este asigurat. n mod implicit, toate excepiile externe au asociat acelai cod (+1) i acelai mesaj (USER DEFINED EXCEPTION). Tratarea unei astfel de erori se face ntr-o manier similar modului de tratare descris anterior. Activarea excepiei externe este fcut explicit, folosind comanda RAISE nsoit de numele excepiei. Comanda oprete execuia normal a blocului PL/SQL i transfer controlul administratorului excepiilor. Declararea i prelucrarea excepiilor externe respect urmtoarea sintax: DECLARE nume_excepie EXCEPTION; -- declarare excepie BEGIN RAISE nume_excepie; --declanare excepie -- codul care urmeaz nu mai este executat EXCEPTION WHEN nume_excepie THEN -- definire mod de tratare a erorii END; Excepiile trebuie privite ca nite variabile, n sensul c ele sunt active n seciunea n care sunt declarate. Ele nu pot s apar n instruciuni de atribuire sau n comenzi SQL. Este recomandat ca fiecare subprogram s aib definit o zon de tratare a excepiilor. Dac pe parcursul execuiei programului intervine o eroare, atunci acesta genereaz o excepie i controlul se transfer blocului de tratare a erorilor. Exemplu: S se scrie un bloc PL/SQL care afieaz numrul creatorilor operelor de art din muzeu care au valoarea mai mare sau mai mic cu 100000$ dect o valoare specificat. S se tipreasc un mesaj adecvat, dac nu exist nici un artist care ndeplinete aceast condiie.
VARIABLE g_mesaj VARCHAR2(100) SET VERIFY OFF ACCEPT p_val PROMPT 'va rog specificati valoarea:' DECLARE v_val opera.valoare%TYPE := &p_val; v_inf opera.valoare%TYPE := v_val - 100000; v_sup opera.valoare%TYPE := v_val + 100000;

372

PROGRAMARE AVANSAT N ORACLE9i

v_numar NUMBER(7); e_nimeni EXCEPTION; e_mai_mult EXCEPTION; BEGIN SELECT COUNT(DISTINCT cod_autor) INTO v_numar FROM opera WHERE valoare BETWEEN v_inf AND v_sup; IF v_numar = 0 THEN RAISE e_nimeni; ELSIF v_numar > 0 THEN RAISE e_mai_mult; END IF; EXCEPTION WHEN e_nimeni THEN :g_mesaj:='nu exista nici un artist cu valoarea operelor cuprinsa intre '||v_inf ||' si '||v_sup; WHEN e_mai_mult THEN :g_mesaj:='exista '||v_numar||' artisti cu valoarea operelor cuprinsa intre '||v_inf||' si '||v_sup; WHEN OTHERS THEN :g_mesaj:='au aparut alte erori'; END; / SET VERIFY ON PRINT g_mesaj

Activarea unei excepii externe poate fi fcut i cu ajutorul procedurii RAISE_APPLICATION_ERROR, furnizat de pachetul DBMS_STANDARD. RAISE_APPLICATION_ERROR poate fi folosit pentru a returna un mesaj de eroare unitii care o apeleaz, mesaj mai descriptiv dect identificatorul erorii. Unitatea apelant poate fi SQL*Plus, un subprogram PL/SQL sau o aplicaie client. Procedura are urmtorul antet: RAISE_APPLICATION_ERROR (numar_eroare IN NUMBER, mesaj_eroare IN VARCHAR2, [ {TRUE | FALSE } ] ); Atributul numar_eroare este un numr cuprins ntre 20000 i 20999, specificat de utilizator pentru excepia respectiv, iar mesaj_eroare este un text asociat erorii, care poate avea maximum 2048 octei. Parametrul boolean este opional. Dac acest parametru este TRUE, atunci noua eroare se va aduga listei erorilor existente, iar dac este FALSE (valoare implicit) atunci noua eroare va nlocui lista curent a erorilor. O aplicaie poate apela RAISE_APPLICATION_ERROR numai dintr-un subprogram stocat (sau metod). Dac procedura RAISE_APPLICATION_ERROR este apelat, atunci subprogramul se termin i sunt returnate codul i mesajul asociate erorii respective. Procedura RAISE_APPLICATION_ERROR poate fi folosit n seciunea

Programare n PL/SQL

373

executabil, n seciunea de tratare a erorilor i chiar simultan n ambele seciuni. n seciunea executabil:
DELETE FROM opera WHERE material = 'carton'; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20201,'info incorecta'); END IF;

n seciunea de tratare a erorilor:

EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20202,'info invalida'); END; n ambele seciuni: DECLARE e_material EXCEPTION; PRAGMA EXCEPTION_INIT (e_material, -20777); BEGIN DELETE FROM opera WHERE valoare < 100001; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20777, 'nu exista opera cu aceasta valoare'); END IF; EXCEPTION WHEN e_material THEN -- trateaza eroarea aceasta END;

RAISE_APPLICATION_ERROR faciliteaz comunicaia dintre client i server, transmind aplicaiei client erori specifice aplicaiei de pe server (de obicei, un declanator). Prin urmare, procedura este doar un mecanism folosit pentru comunicaia server client a unei erori definite de utilizator, care permite ca procesul client s trateze excepia. Exemplu: S se implementeze un declanator care nu permite acceptarea n muzeu a operelor de art avnd valoarea mai mic de 100000$.
CREATE OR REPLACE TRIGGER minim_valoare BEFORE INSERT ON opera FOR EACH ROW BEGIN IF :NEW.valoare < 100000 THEN RAISE_APPLICATION_ERROR (-20005,operele de arta trebuie sa aiba valoare mai mare de 100000$); END IF;

374
END;

PROGRAMARE AVANSAT N ORACLE9i

Pe staia client poate fi scris un program care detecteaz i trateaz eroarea.


DECLARE /* declarare excepie */ nu_accepta EXCEPTION; /* asociaz nume,codului eroare folosit in trigger */ PRAGMA EXCEPTION_INIT(nu_accepta,-20005); BEGIN /* incearca sa inserezi */ INSERT INTO opera ; EXCEPTION /* tratare exceptie */ WHEN nu_accepta THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); /* SQLERRM va returna mesaj din RAISE_APPLICATION_ERROR */ END;

Cazuri speciale n tratarea excepiilor


Dac se declaneaz o excepie ntr-un bloc simplu, atunci se face saltul la partea de tratare (handler) a acesteia, iar dup ce este terminat tratarea erorii se iese din bloc (instruciunea END). Prin urmare, dac excepia se propag spre blocul care include blocul curent, restul aciunilor executabile din subbloc sunt pierdute. Dac dup o eroare se dorete totui continuarea prelucrrii datelor, este suficient ca instruciunea care a declanat excepia s fie inclus ntr-un subbloc. Dup ce subblocul a fost terminat, se continu secvena de instruciuni din blocul principal. Exemplu:
BEGIN DELETE SELECT --poate declansa exceptia A --nu poate fi efectuat INSERT care urmeaza INSERT INTO EXCEPTION WHEN A THEN END;

Deficiena anterioar se poate rezolva incluznd ntr-un subbloc comanda SELECT care a declanat excepia.
BEGIN DELETE BEGIN SELECT EXCEPTION WHEN A THEN /* dupa ce se trateaza exceptia A, controlul este

Programare n PL/SQL transferat blocului de nivel superior, de fapt comenzii INSERT */ END; INSERT INTO EXCEPTION END;

375

Uneori este dificil de aflat care comand SQL a determinat o anumit eroare, deoarece exist o singur seciune pentru tratarea erorilor unui bloc. Sunt sugerate dou soluii pentru rezolvarea acestei probleme.
1) Introducerea unui contor care s identifice instruciunea SQL. DECLARE v_sel_cont NUMBER(2):=1; BEGIN SELECT v_sel_cont:=2; SELECT v_sel_cont:=3; SELECT EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO log_table(info) VALUES ('comanda SELECT ' || TO_CHAR(v_sel_cont) || ' nu gaseste date'); END; 2) Introducerea fiecrei instruciuni SQL ntr-un subbloc. BEGIN BEGIN SELECT EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO log_table(info) VALUES('SELECT 1 nu gaseste date'); END; BEGIN SELECT EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO log_table(info) VALUES('SELECT 2 nu gaseste date'); END; END;

Activarea excepiilor

376

PROGRAMARE AVANSAT N ORACLE9i

Pentru activarea unei excepii exist dou metode: activarea explicit a excepiei (definite de utilizator sau predefinite) n interiorul blocului, cu ajutorul comenzii RAISE; activarea automat a excepiei asociate unei erori Oracle. Excepiile pot fi sesizate n seciunea executabil, declarativ sau n cea de tratare a excepiilor. La aceste niveluri ale programului, o excepie poate fi gestionat n moduri diferite. Pentru a reinvoca o excepie, dup ce a fost tratat n blocul curent, se folosete instruciunea RAISE, dar fr a fi nsoit de numele excepiei. n acest fel, dup executarea instruciunilor corespunztoare tratrii excepiei, aceasta se transmite i blocului printe. Pentru a fi recunoscut ca atare de ctre blocul printe, excepia trebuie s nu fie definit n blocul curent, ci n blocul printe (sau chiar mai sus n ierarhie), n caz contrar ea putnd fi captat de ctre blocul printe doar la categoria OTHERS. Pentru a executa acelai set de aciuni n cazul mai multor excepii nominalizate explicit, n seciunea de prelucrare a excepiilor se poate utiliza operatorul OR. Pentru a evita tratarea fiecrei erori n parte, se folosete seciunea WHEN OTHERS care va cuprinde aciuni pentru fiecare excepie care nu a fost tratat, adic pentru captarea excepiilor neprevzute sau necunoscute. Aceast seciune trebuie utilizat cu atenie deoarece poate masca erori critice sau poate mpiedica aplicaia s rspund n mod corespunztor.

Propagarea excepiilor
Dac este declanat o eroare n seciunea executabil i blocul curent are un handler pentru tratarea ei, atunci blocul se termin cu succes, iar controlul este dat blocului imediat exterior. Dac se produce o excepie care nu este tratat n blocul curent, atunci excepia se propag spre blocul printe, iar blocul PL/SQL curent se termin fr succes. Procesul se repet pn cnd fie se gsete ntr-un bloc modalitatea de tratare a erorii, fie se oprete execuia i se semnaleaz situaia aprut (unhandled exception error). Dac este declanat o eroare n partea declarativ a blocului, aceasta este propagat ctre blocul imediat exterior, chiar dac exist un handler al acesteia n blocul corespunztor seciunii declarative. La fel se ntmpl dac o eroare este declanat n seciunea de tratare a erorilor. La un moment dat, ntr-o seciune EXCEPTION, poate fi activ numai o singur excepie. Instruciunea GOTO nu permite: saltul la seciunea de tratare a unei excepii; saltul de la seciunea de tratare a unei excepii, n blocul curent. Comanda GOTO permite totui saltul de la seciunea de tratare a unei excepii la un bloc care include blocul curent.

Programare n PL/SQL

377

Exemplu: Exemplul urmtor marcheaz un salt ilegal n blocul curent.


DECLARE v_var NUMBER(10,3); BEGIN SELECT dim2/NVL(valoare,0) INTO v_var FROM opera WHERE dim1 > 100; <<eticheta>> INSERT INTO politaasig(cod_polita, valoare) VALUES (7531, v_var); EXCEPTION WHEN ZERO_DIVIDE THEN v_var:=0; GOTO <<eticheta>>; --salt ilegal in blocul curent END;

n continuare, vor fi analizate modalitile de propagare a excepiilor n cele trei cazuri comentate: excepii sesizate n seciunea declarativ, n seciunea executabil i n seciunea de tratare a erorilor.

Excepie sesizat n seciunea executabil


Excepia este sesizat i tratat n subbloc. Dup aceea, controlul revine blocului exterior.
DECLARE A EXCEPTION; BEGIN BEGIN RAISE A; -- exceptia A sesizata in subbloc EXCEPTION WHEN A THEN -- exceptia tratata in subbloc END; -- aici este reluat controlul END;

Excepia este sesizat n subbloc, dar nu este tratat n acesta i atunci se propag spre blocul exterior. Regula poate fi aplicat de mai multe ori.
DECLARE A EXCEPTION; B EXCEPTION; BEGIN BEGIN RAISE B; --exceptia B sesizata in subbloc EXCEPTION WHEN A THEN --exceptia B nu este tratata in subbloc

378

PROGRAMARE AVANSAT N ORACLE9i

END; EXCEPTION WHEN B THEN /* exceptia B s-a propagat spre blocul exterior unde a fost tratata, apoi controlul trece in exteriorul blocului */ END;

Excepie sesizat n seciunea declarativ


Dac n seciunea declarativ este generat o excepie, atunci aceasta se propag ctre blocul exterior, unde are loc tratarea acesteia. Chiar dac exist un handler pentru excepie n blocul curent, acesta nu este executat. Exemplu: S se realizeze un program prin care s se exemplifice propagarea erorilor aprute n seciunea declarativ a unui bloc PL/SQL. Programul calculeaz numrul creatorilor de opere de art care au lucrri expuse n muzeu.
BEGIN DECLARE nr_artisti NUMBER(3) := 'XYZ'; BEGIN SELECT COUNT (DISTINCT cod_autor) INTO nr_artisti FROM opera; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Eroare bloc intern:' || SQLERRM); END; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Eroare bloc extern:' || SQLERRM ); END;

Deoarece la iniializarea variabilei nr_artisti apare o neconcordan ntre tipul declarat i cel asignat, este generat eroarea intern VALUE_ERROR. Cum eroarea a aprut n partea declarativ a blocului intern, dei acesta conine un handler OTHERS care ar fi putut capta eroarea, handler-ul nu este executat, eroarea fiind propagat ctre blocul extern unde este tratat n handler-ul OTHERS asociat. Aceasta se poate remarca deoarece la execuie se obine mesajul: Eroare bloc extern: ORA-06502: PL/SQL: numeric or value error.

Excepie sesizat n seciunea EXCEPTION


Dac excepia este sesizat n seciunea EXCEPTION, ea se propag imediat spre blocul exterior.
BEGIN DECLARE A EXCEPTION; B EXCEPTION; BEGIN

Programare n PL/SQL RAISE A; --sesizare exceptie A EXCEPTION WHEN A THEN RAISE B; --sesizare exceptie B WHEN B THEN /* exceptia este propagata spre blocul exterior cu toate ca exista aici un handler pentru ea */ END; EXCEPTION WHEN B THEN --exceptia B este tratata in blocul exterior END;

379

Informaii despre erori


Pentru a obine textul corespunztor erorilor la compilare, poate fi utilizat vizualizarea USER_ERRORS din dicionarul datelor. Pentru informaii adiionale referitoare la erori pot fi consultate vizualizrile ALL_ERRORS sau DBA_ERRORS. Vizualizarea USER_ERRORS are cmpurile: NAME (numele obiectului), TYPE (tipul obiectului), SEQUENCE (numrul secvenei), LINE (numrul liniei din codul surs n care a aprut eroarea), POSITION (poziia n linie unde a aprut eroarea) i TEXT (mesajul asociat erorii). Exemplu: S se afieze erorile de compilare din procedura alfa.
SELECT LINE, POSITION, TEXT FROM USER_ERRORS WHERE NAME = 'ALFA';

LINE specific numrul liniei n care apare eroarea, dar acesta nu corespunde liniei efective din fiierul text (se refer la codul surs depus n USER_SOURCE). Dac nu sunt erori, apare mesajul NO ROWS SELECTED.

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