Pentru executia cererilor SQL prezente in blocurile PL/SQL si pentru stocarea rezultatelor acestora (cum e cazul cererilor SELECT) Oracle foloseste un spatiu de lucru accesibil utilizatorului prin intermediul unui obiect numit cursor. Acesta poate fi de exemplu folosit pentru a parcurge linie cu linie rezultatul si pentru a afla cate linii are acesta.
Cursorii sunt de doua tipuri:
§ Cursori impliciti. Ei sunt definiti automat de Oracle pentru orice cerere de tip DML, inclusiv pentru cererile SELECT .. INTO care returneaza o singura linie prezentate in capitolele anterioare. Acesti cursori nu au un nume declarat de utilizator iar cererea SQL asociata lor este ultima cerere SQL executata.
§ Cursori expliciti. Acestia sunt definiti de utilizator care precizeaza numele cursorului si cererea SQL asociata.
Pentru a folosi cursori expliciti pasii sunt
urmatorii:
§
In
zona de declaratii se defineste cursorul (nume, cerere SQL asociata).
§
In
zona executabila (dupa BEGIN, deci inclusiv in zona de tratare a exceptiilor)
se pot folosi instructiunile de lucru cu un cursor: OPEN, FETCH si CLOSE.
§
Pentru
orice tip de cursor se pot folosi metodele asociate acestora: %ISOPEN,
%NOTFOUND, %FOUND si %ROWCOUNT
Observatii:
§
Instructiunile
OPEN, FETCH si CLOSE sunt specifice cursorilor expliciti si nu pot fi folosite
pentru cei impliciti (pentru acestia operatiile respective sunt executate
automat de Oracle).
§
Pentru
cursorii impliciti valorile atributelor acestora (%NOTFOUND, %FOUND si
%ROWCOUNT) se pot afla folosind un nume de cursor generic - SQL - care desemneaza cel mai recent cursor
implicit deschis.
Ultima cerere executata a unui bloc PL/SQL are
asociat un cursor cu numele generic SQL.
Trebuie remarcat ca spre deosebire de cererile
SELECT . . INTO, celelalte cereri DML nu genereaza exceptii in cazul in care
nici o linie nu este afectata (de exemplu in cazul DELETE sau UPDATE).
Atributele cursorului SQL vor avea urmatoarele
valori:
§
SQL%ROWCOUNT:
numarul de linii afectate de cerere
§
SQL%FOUND:
TRUE daca exista linii afectate de cerere si FALSE altfel
§
SQL%NOTFOUND:
TRUE daca nu exista linii afectate de cerere si FALSE altfel
§
SQL%ISOPEN:
nu se aplica cursorilor impliciti.
Exemplu:
BEGIN
UPDATE EMP
SET SAL = SAL * 1.1
WHERE DEPTNO = 30;
dbms_output.put_line('Au fost
modificate '||
SQL%ROWCOUNT || ' linii');
if sql%found then
dbms_output.put_line('Found TRUE');
else
dbms_output.put_line('Found FALSE');
end if;
if sql%notfound then
dbms_output.put_line('NotFound TRUE');
else
dbms_output.put_line('NotFound FALSE');
end if;
END;
Asa cum am mentionat pasii de lucru sunt:
A. Declararea cursorului
Sintaxa
declaratiei unui cursor este:
DECLARE
CURSOR nume_cursor IS
cerere_select;
§
nume_cursor este un identificator
PL/SQL care denumeste cursorul.
§
cerere_select este o cerere SELECT care
NU trebuie sa contina clauza INTO. Variabila/variabilele in care se va incarca
linia curenta din rezultat va fi setata de catre instructiunea FETCH.
B. Deschiderea cursorului
Prin executia instructiunii OPEN se 'deschide' un
cursor. Efectul practic este urmatorul:
§
se executa cererea SQL asociata cursorului.
§
se
aduce in zona de memorie a acestuia rezultatul cererii (o tabela rezultat).
§
se
seteaza inregistrarea (linia) curenta la prima linie
Sintaxa instructiunii este:
OPEN nume_cursor;
Observatie: daca cererea SQL nu intoarce nici o
linie sau intoarce mai multe linii NU se ridica exceptiile NO_DATA_FOUND
respectiv TOO_MANY_ROWS.
C. Incarcarea liniei curente
Rezultatul lui OPEN este o tabela rezultat iar
indicatorul liniei curente este setat la prima.
Prin instructiunea FETCH:
§
Se
incarca linia curenta intr-o multime de variabile sau o singura variabila
inregistrare (avand mai multe campuri). Corespondenta este pozitionala
§
Se
incrementeaza indicatorul liniei curente.
§
Daca
nu mai sunt linii %FOUND intoarce FALSE
Sintaxa instructiunii este:
FETCH nume_cursor INTO
lista_variabile |
variabila_inregistrare
In cazul in care incarcarea se face intr-o lista de
variabile trebuie ca:
§
numarul
de variabile din lista sa fie acelasi cu numarul de coloane din rezultatul
cererii SELECT
§
tipurile
variabilelor din lista sa fie aceleasi cu tipurile coloanelor din rezultatul
cererii SELECT
In cazul in care incarcarea se face intr-o variabila
inregistrare trebuie ca:
§
numarul
de campuri al variabilei sa fie acelasi cu numarul de coloane din rezultatul
cererii SELECT
§
tipurile
campurilor variabilei sa fie aceleasi cu tipurile coloanelor din rezultatul
cererii SELECT
§
Se
poate defini o variabila de acelasi tip cu liniile rezultatului folosind
constructia nume_cursor%rowtype.
Atributelor cursorului pot fi consultate dupa ce s-a
facut primul FETCH si ele au urmatoarele valori:
§
nume_cursor%ROWCOUNT:
numarul de linii incarcate cu FETCH de la deschiderea cursorului.
§
nume_cursor%FOUND:
TRUE daca ultimul FETCH a incarcat o noua linie si FALSE altfel.
§
nume_cursor%NOTFOUND:
TRUE daca ultimul FETCH nu a incarcat o noua linie si FALSE altfel.
§
nume_cursor%ISOPEN:
TRUE daca acel cursor e deschis si FALSE altfel
Tabelul urmator arata dependenta valorilor unor
atribute de starea cursorului:
Folosit |
|
%FOUND |
%ISOPEN |
%NOTFOUND |
%ROWCOUNT |
inainte de |
OPEN |
exceptie |
FALSE |
exceptie |
exceptie |
dupa |
OPEN |
NULL |
TRUE |
NULL |
0 |
inainte de |
Primul FETCH |
NULL |
TRUE |
NULL |
0 |
dupa |
Primul FETCH |
TRUE |
TRUE |
FALSE |
1 |
inainte de |
Urmatoarele FETCH |
TRUE |
TRUE |
FALSE |
1 |
dupa |
Urmatoarele FETCH |
TRUE |
TRUE |
FALSE |
depinde de date |
inainte de |
Ultimul FETCH |
TRUE |
TRUE |
FALSE |
depinde de date |
dupa |
Ultimul FETCH |
FALSE |
TRUE |
TRUE |
depinde de date |
inainte de |
CLOSE |
FALSE |
TRUE |
TRUE |
depinde de date |
dupa |
CLOSE |
exceptie |
FALSE |
exceptie |
exceptie |
Nota: Ultimul FETCH este cel care nu mai intoarce o
linie din rezultat.
Exemple:
1.
Listarea
numelor angajatilor din departamentul 30. Parcurgerea rezultatului se face cu
un ciclu LOOP si iesirea este determinata de atributul %NOTFOUND:
declare
-- declarare cursor
cursor angajati is
SELECT * FROM EMP WHERE DEPTNO = 30;
-- variabila inregistrare pentru incarcare linie
om angajati%rowtype;
begin
-- deschidere cursor
open angajati;
loop
-- incarcarea liniei curente
fetch angajati into om;
-- iesire cand nu mai sunt linii
exit when angajati%notfound;
dbms_output.put_line(angajati%rowcount||
'
Angajatul: '||om.ename);
end loop;
-- inchidere cursor
close angajati;
end;
Rezultatul executiei este:
1 Angajatul: ALLEN
2 Angajatul: WARD
3 Angajatul: MARTIN
4 Angajatul: TURNER
5 Angajatul: CLARK
6 Angajatul: JAMES
2.
Rezultate
returnate de ISOPEN, FOUND
declare
cursor angajati is
SELECT * FROM EMP WHERE DEPTNO = 30;
om angajati%rowtype;
begin
-- inca nu e deschis
if angajati%isopen then
dbms_output.put_line('Cursorul e deschis');
else
dbms_output.put_line('Cursorul nu e deschis');
end if;
-- se deschide cursorul
open angajati;
if angajati%isopen then
dbms_output.put_line('Cursorul e deschis');
else
dbms_output.put_line('Cursorul nu e deschis');
end if;
-- este deschis, se incarca o
linie
fetch angajati into om;
if angajati%found then
dbms_output.put_line('Exista rezultat');
else
dbms_output.put_line('Nici macar o linie');
end if;
-- inchidere cursor
close angajati;
if angajati%isopen then
dbms_output.put_line('Cursorul e deschis');
else
dbms_output.put_line('Cursorul nu e deschis');
end if;
end;
Rezultatul executiei blocului de mai sus este:
Cursorul nu e deschis
Cursorul e deschis
Exista rezultat
Cursorul nu e deschis
Pentru a usura parcurgerea liniilor tabelei rezultat
asociata unui cursor deschis in PL/SQL exista un ciclu FOR special.
Sintaxa lui este urmatoarea:
FOR nume_inregistrare IN
nume_cursor LOOP
instructiuni;
END LOOP
Observatii:
§
Variabila
inregistrare NU trebuie definita de utilizator, acest lucru facandu-se automat.
§
Domeniul
de valabilitate al acestei variabile este corpul ciclului.
§
OPEN,
FETCH si CLOSE se executa automat
§
Se
executa cate un pas al ciclului pentru fiecare linie a rezultatului
§
Iesirea
din ciclu se face automat la terminarea liniilor.
Exemplu:
declare
cursor angajati is
SELECT * FROM EMP WHERE DEPTNO = 30;
begin
for om in angajati loop --
deschide automat cursorul
-- nu e necesar FETCH
dbms_output.put_line(angajati%rowcount||
' Angajatul: '||om.ename);
end loop; -- inchide automat cursorul
end;
Rezultatul este identic cu cel de la 7.3. Exemplul
1.
Acest tip de ciclu FOR se poate folosi si pentru cursori
impliciti, ca in exemplul de mai jos:
begin
-- nu e necesara
declararea cursorului
for om in (select * from
emp where deptno=30) loop
-- nu e necesar FETCH
dbms_output.put_line(sql%rowcount||
' Angajatul: '||om.ename);
end loop;
-- nu e necesara
inchiderea cursorului
end;
In acest caz returnarea a 0 sau mai multor linii nu duce la ridicarea de exceptii.
Unui cursor i se pot transmite parametri. In acest
caz:
§
La
declarare se specifica parametrii formali si tipul lor (fara sa se specifice
dimensiunea, deci VARCHAR2 si nu VARCHAR2(10) de exemplu). Acesti parametri
sunt folositi in cererea SQL asociata.
§
La
deschidere se specifica parametrii actuali.
Sintaxa declararii unui parametru este:
parametru [IN] tipdedate [{:= | DEFAULT} expression]
Exemplu:
declare
cursor angajati (v_dept number :=10,
v_job varchar2:='CLERK') is
select ename, sal
from emp
where deptno = v_dept and job = v_job;
i number;
begin
for i in 1..5 LOOP -- cod
departament = i*10
dbms_output.put_line('Departamentul
'||i*10);
for om in angajati(i*10,
'MANAGER') loop
-- deschide automat cursorul
-- nu e necesar FETCH
dbms_output.put_line(angajati%rowcount||
' Angajatul: '||om.ename);
end loop; -- inchide automat cursorul
end loop;
dbms_output.put_line('Cu
parametrii impliciti');
for om in angajati loop
-- deschide automat cursorul
-- nu e necesar FETCH
dbms_output.put_line(angajati%rowcount||
' Angajatul: '||om.ename);
end loop; -- inchide automat cursorul
end;
Rezultatul este:
Departamentul 10
1 Angajatul: BLAKE
Departamentul 20
1 Angajatul: JONES
Departamentul 30
1 Angajatul: CLARK
Departamentul 40
Departamentul 50
Cu parametrii impliciti
1 Angajatul: MILLER
In clauza FROM a cererii SQL care defineste un
cursor poate sa apara o subcerere SQL. Ea se evalueaza o singura data si
rezultatul sau este folosit pentru calculul rezultatului cererii globale.
Exemplu: declaratia cursorului
contine (pe clauza FROM a cererii) o subcerere SQL care returneaza o tabela cu
doua coloane continand numarul departamentului si numarul de angajati pentru
departamentele cu mai mult de 4 angajati.
declare
cursor d is select
t1.deptno, dname, cati
from dept t1, (select
deptno, count(*) cati
from emp group by deptno) t2
where t1.deptno =
t2.deptno AND cati > 4;
begin
for dep in d loop
dbms_output.put_line(d%rowcount||
' Departamentul: '||
dep.deptno||
' '||dep.dname||
' '||dep.cati);
end loop;
end;
Rezultatul
va fi:
1 Departamentul: 20
RESEARCH 5
2 Departamentul: 30 SALES
6
Bineinteles subcererile pot fi prezente si pe clauza
WHERE, fie corelate, fie necorelate.
Aceste referinte (de tip REF CURSOR) sunt similare pointerilor
din limbajele C sau Pascal.
Definirea se face cu:
TYPE nume_tip_pointer IS REF CURSOR [RETURN
tip_inregistrare];
v_pointer_cursor nume_tip_pointer;
Clauza RETURN se foloseste in definirea pachetelor
(package) pentru a specifica tipul de date intors de cursor.
Deschiderea cursorului se face in acest caz cu:
OPEN v_pointer_cursor FOR cerere_sql;
Deci asocierea cererii SQL se face nu la declarare
ci la deschiderea cursorului.
Exemplu:
declare
type Referinta is ref
cursor return emp%rowtype;
c_ref Referinta; -- variabila de tip REF CURSOR
e emp%rowtype; -- variabila pentru incarcarea
-- unei linii
begin
-- deschidere cursor cu
specificarea cererii
open c_ref for select *
from emp where deptno=10;
loop
fetch c_ref into e;
exit when c_ref%notfound;
dbms_output.put_line(c_ref%rowcount||
' Nume: '||
e.ename||
' Salariu '||
e.sal);
end loop;
close c_ref;
-- deschidere cursor cu
alta cerere SQL
open c_ref for select *
from emp where job='CLERK';
loop
fetch c_ref into e;
exit when c_ref%notfound;
dbms_output.put_line(c_ref%rowcount||
' Nume: '||
e.ename||
' Functia '||
e.job);
end loop;
END;
Rezultatul va fi:
1 Nume: BLAKE Salariu 5962
2 Nume: MILLER Salariu
2529
3 Nume: KING Salariu 5600
1 Nume: SMITH Functia
CLERK
2 Nume: ADAMS Functia
CLERK
3 Nume: JAMES Functia
CLERK
4 Nume: MILLER Functia
CLERK
Printre
restrictiile privind variabilele REF CURSOR mentionam:
§
Nu se pot
declara in pachete (package)
§
Nu se pot
compara pentru egalitate, inegalitate sau valoare nula
§
Nu li se poate
asigna o valoare nula
§
Tipul REF CURSOR
nu poate fi folosit in SQL la definirea coloanelor unei tabele sau vederi.
§
Nu poate fi
tipul elementelor unei colectii
§
Nu sunt
interschimbabile cu variabilele tip cursor: de exemplu nu le putem folosi in
cicluri FOR pentru cursori (mai sus am folosit LOOP).
Cand se incearca incarcarea unei linii intr-o
variabila care nu corespunde ca tip se ridica exceptia ROWTYPE_MISMATCH.
In exemplul de mai jos se incearca incarcarea unei
linii cu mai multe valori intr-o variabila scalara. In acest caz se va ridica
exceptia mentionata:
declare
type Referinta is ref
cursor;
c_ref Referinta; -- variabila de tip REF CURSOR
e varchar2(10); -- variabila pentru incarcarea
-- unei linii. NU E CORECTA!
begin
-- deschidere cursor cu
specificarea cererii
open c_ref for select *
from emp where deptno=10;
loop
fetch c_ref into e; --
aici se ridica exceptia.
exit when c_ref%notfound;
end loop;
close c_ref;
exception
when rowtype_mismatch then
dbms_output.put_line('Exceptie rowtype_mismatch');
end;
In cazul in care prin intermediul unui cursor se
executa operatii de actualizare a tabelelor se poate folosi clauza FOR UPDATE
pentru a bloca liniile necesare in momentul in care se deschide cursorul
(implicit Oracle blocheaza o linie in momentul in care aceasta e actualizata).
De asemenea, cand se foloseste CURRENT OF pentru a
modifica liniile unei tabele acestea trebuie blocate inainte cu FOR UPDATE.
Sintaxa este:
DECLARE
CURSOR nume_cursor
[(parametri)] IS
SELECT . . .
. .
.
FOR
UPDATE [OF coloana] [NOWAIT]
In momentul deschiderii cursorului liniile gasite de
cererea SELECT sunt blocate. Ele sunt deblocate la sfarsitul tranzactiei
(COMMIT). Dupa ce modificarile au fost comise nu se mai pot incarca linii
dintr-un cursor definit cu FOR UPDATE.
NOWAIT specifica faptul ca daca liniile respective
sunt blocate in acel moment Oracle sa nu astepte (aceasta fiind actiunea
standard).
Exemplu: liniile tabelei EMP sunt blocate pentru ca
pe parcursul actualizarii sa nu fie modificate de alti utilizatori:
declare
cursor c_ang is select
ename, job, rowid
from emp FOR UPDATE NOWAIT;
v_nume emp.ename%type;
v_job emp.job%type;
v_rowid rowid; v_nr
number;
begin
open c_ang; v_nr := 0;
loop
fetch c_ang into v_nume, v_job, v_rowid;
exit when c_ang%notfound;
dbms_output.put_line(v_nume||' '||v_job||' '||v_rowid);
update emp set sal = sal * 1.1 where rowid = v_rowid;
-- mai usor aici ar fi CURRENT OF c_ang
v_nr := v_nr + 1;
end loop;
dbms_output.put_line('Actualizate:
'||v_nr);
close c_ang;
commit;
exception
when others then
dbms_output.put_line(SQLCODE||' '||SQLERRM);
end;
/
In cazul in care exista linii blocate de alti
utilizatori se va genera o exceptie (din cauza lui NOWAIT). Mesajul va fi:
-54 ORA-00054: resource busy and acquire with NOWAIT
specified
PL/SQL procedure successfully completed.
Daca liniile nu sunt blocare de alt utilizator
rezultatul este:
SMITH CLERK AAAT1wAAEAAABSFAAA
ALLEN SALESMAN AAAT1wAAEAAABSFAAB
WARD SALESMAN AAAT1wAAEAAABSFAAC
MARTIN SALESMAN AAAT1wAAEAAABSFAAD
TURNER SALESMAN AAAT1wAAEAAABSFAAE
JONES MANAGER AAAT1wAAEAAABSFAAF
BLAKE MANAGER AAAT1wAAEAAABSFAAG
CLARK MANAGER AAAT1wAAEAAABSFAAH
ADAMS CLERK AAAT1wAAEAAABSFAAI
JAMES CLERK AAAT1wAAEAAABSFAAJ
MILLER CLERK AAAT1wAAEAAABSFAAK
SCOTT ANALYST AAAT1wAAEAAABSFAAL
FORD ANALYST AAAT1wAAEAAABSFAAM
KING PRESIDENT AAAT1wAAEAAABSFAAN
Actualizate: 14
PL/SQL procedure successfully completed.
De asemenea prin clauza FOR UPDATE se blocheaza linii
dintr-o tabela aflata pe clauza FROM a unei cereri SQL multi-tabela. Pentru
aceasta trebuie specificata coloana care se doreste modificata:
FOR UPDATE OF coloana
Exemplu:
declare
cursor c_ang is select
ename, e.rowid, dname
from emp e,dept d
where e.deptno=d.deptno
and d.dname='ACCOUNTING'
FOR UPDATE of sal nowait;
v_nume emp.ename%type;
v_dname dept.dname%type;
v_rowid rowid; v_nr
number;
begin
open c_ang;
v_nr := 0;
loop
fetch c_ang into v_nume, v_rowid, v_dname;
exit when c_ang%notfound;
dbms_output.put_line(v_nume||' '||v_dname);
update emp set sal = sal * 1.1 where rowid = v_rowid;
-- mai usor aici ar fi CURRENT OF c_ang
v_nr
:= v_nr + 1;
end loop;
dbms_output.put_line('Actualizate:
'||v_nr);
close c_ang;
commit;
exception
when others then
dbms_output.put_line(SQLCODE||' '||SQLERRM);
end;
Rezultat:
BLAKE ACCOUNTING
MILLER ACCOUNTING
KING ACCOUNTING
Actualizate: 3
In cazul in care folosim FOR UPDATE liniile blocate
se pot identifica si prin constructia:
CURRENT OF nume_cursor
folosita in WHERE.
Exemplu:
declare
cursor c_ang is select
ename, job
from emp FOR UPDATE;
v_nume emp.ename%type;
v_job emp.job%type;
v_nr number;
begin
open c_ang; v_nr := 0;
loop
fetch c_ang into v_nume, v_job;
exit when c_ang%notfound;
dbms_output.put_line(v_nume||' '||v_job);
update emp
set sal = sal * 1.1
where CURRENT OF c_ang;
-- aici e folosita constructia
v_nr := v_nr + 1;
commit;
end loop;
dbms_output.put_line('Actualizate:
'||v_nr);
close c_ang;
exception
when others then
dbms_output.put_line(SQLCODE||' '||SQLERRM);
end;
Rezultatul va fi:
SMITH CLERK
ALLEN SALESMAN
WARD SALESMAN
MARTIN SALESMAN
TURNER SALESMAN
JONES MANAGER
BLAKE MANAGER
CLARK MANAGER
ADAMS CLERK
JAMES CLERK
MILLER CLERK
SCOTT ANALYST
FORD ANALYST
KING PRESIDENT
Actualizate: 14
PL/SQL procedure
successfully completed.