Professional Documents
Culture Documents
10 Curs Cursor
10 Curs Cursor
Un cursor în SQL este un obiect care permite parcurgerea și manipularea înregistrărilor dintr-un
rezultat al unei interogări.
Este folosit pentru a itera prin rândurile returnate de o interogare și pentru a accesa valorile
individuale ale acestora.
Cursorul din MySQL este aproape identic cu un cursor din alte baze de date.
Cursorul MySQL este doar pentru citire, nu poate fi derulat și este “sensitive”.
Operaţiile curente asociate cursoarelor sunt (în ordinea în care acestea trebuie apelate)
sunt:
declararea;
deschiderea;
parcurgerea;
închiderea.
1
Caracteristicile unui cursor sunt:
1. asenzitivitatea: serverul poate realiza sau nu copii ale tabelului care conţine rezultatele
care sunt parcurse;
2. proprietatea de a nu putea fi suprascrise;
3. proprietatea de a putea fi parcurse într-o singură direcţie şi în ordine.
Crearea unei structuri de tip cursor se realizeaza prin intermediul instructiunii DECLARE, astfel:
DECLARE nume_cursor CURSOR FOR instructiune_select
Operaţia de tip SELECT care este asociată cursorului nu poate avea clauza INTO. O rutină stocată poate
defini mai multe cursoare însă fiecare trebuie identificat printr-o denumire unică.
FROM film
O procedura stocata poate contine mai multe declaratii ale unor cursori, atata timp cat acestia
prezinta denumiri diferite.
Declarațiile de cursor trebuie să apară după toate declarațiile variabilelor noastre. Declararea
unui cursor înainte de a declara variabilele noastre generează eroarea 1337.
Exemplul:
2
DECLARE i INT;
END;
Utilizarea unui cursor are in vedere realizarea urmatoarelor operatii: deschiderea cursorului,
citirea de inregistrari de la nivelul cursorului, respectiv inchiderea cursorului.
Dupa declararea unui cursor, acesta poate fi deschis prin intermediul instructiunii OPEN.
OPEN film_cursor;
Inchiderea explicita a unui cursor deschis se realizeaza la nivelul unei instructiuni de tip CLOSE.
Aceasta operatie are in vederea dezactivarea cursorului si eliberarea memoriei asociate
acestuia.
CLOSE film_cursor;
Daca se incearca inchiderea unui cursor care nu a fost deschis anterior, este generata o eroare.
Inchiderea unui cursor se realizeaza automat la finalul unui bloc de tip BEGIN … END, daca nu a
fost solicitata inchiderea explicita a acestuia.
3
Utilizarea datelor de la nivelul cursorilor
Inregistrarile de la nivelul rezultatului interogarii asociate unui cursor pot fi parcurse, una cate
una, prin intermediul instructiunii FETCH. In cadrul sistemelor de tip MySQL, instructiunea
FETCH prezinta urmatoarea sintaxa generala:
Daca sunt prezente inregistrari la nivelul rezultatului interogarii, executia instructiunii FETCH
determina stocarea valorilor coloanelor in variabilele precizate dupa clauza INTO. Numarul de
coloane precizate in interogarea asociata unui cursor trebuie sa corespunda cu numarul de
variabile precizate la apelul instructiunii FETCH.
In momentul in care nu mai sunt disponibile inregistrari din rezultat, este generata o conditie
care precizeaza acest aspect. O astfel de situatie poate fi gestionata prin intermediul unui obiect
handler de tip NOT FOUND. Declaratia obiectului handler de tip NOT FOUND se realizeaza
astfel:
Se poate observa ca la nivelul acestei declaratii este actualizata si valoarea variabilei done,
variabila care initial primeste valoarea 0 si care indica faptul ca structura de tip cursor a ajuns la
finalul rezultatului interogarii asociate.
SET l_last_row_fetched=0;
OPEN cursor1;
cursor_loop:LOOP
FETCH cursor1 INTO l_customer_name,l_contact_surname,l_contact_firstname;
IF l_last_row_fetched=1 THEN
LEAVE cursor_loop;
END IF;
/*Do something with the row fetched*/
END LOOP cursor_loop;
CLOSE cursor1;
SET l_last_row_fetched=0;
4
Reteta cursor
2. Deschiderea cursorului:
OPEN cursor_name
3. Obţinerea valorilor:
4. Închiderea cursorului:
CLOSE cursor_name
Exemplu
USE sakila;
DROP PROCEDURE IF EXISTS get_film_actors;
DELIMITER &&
CREATE PROCEDURE get_film_actors(IN film_title VARCHAR(128))
BEGIN
DECLARE done INTEGER DEFAULT 0;
DECLARE current_film_id INT;
DECLARE current_title VARCHAR(128) DEFAULT '';
DECLARE film_cursor CURSOR FOR
SELECT film_id, title
FROM film
WHERE title LIKE CONCAT('%', film_title, '%');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN film_cursor;
get_actors: LOOP
FETCH film_cursor INTO current_film_id, current_title;
5
IF done = 1 THEN
LEAVE get_actors;
END IF;
SELECT current_title;
SELECT first_name, last_name
FROM actor INNER JOIN film_actor USING(actor_id)
INNER JOIN film USING(film_id)
WHERE film_id = current_film_id;
END LOOP;
CLOSE film_cursor;
END &&
DELIMITER ;
Procedura stocata get_film_actors este definita la nivelul bazei de date sakila pentru a afisa
numele actorilor pentru toate filmele care contin in titlu sirul de caractere furnizat ca si
parametru de intrare. Valoarea corespunzatoare acestui parametru se utilizeaza la nivelul
interogarii asociate cursorului, intr-o expresie care permite filtrare inregistrarilor (clauza
WHERE).
Dupa deschiderea cursorului, si executia interogarii asociate acestuia, are loc parcurgerea
inregistrarilor din rezultat prin intermediul unei structuri iterative de tip LOOP (get_actors).
Aceasta structura contine in prima linie instructiunea FETCH, care preia de la nivelul cursorului
film_cursor datele corespunzatoare fiecarei inregistrari. In acest caz, din cursor sunt preluate
valorile corespunzatoare coloanelor film_id si title, valori care sunt stocate in variabilele
current_film_id, respectiv current_title.
La nivelul structurii de control iterative (LOOP) cele doua variabile sunt utilizate in doua
instructiuni SELECT. Prima instructiune SELECT afiseaza titlul filmului curent. Variabila
current_film_id este utilizata in cea de-a doua instructiune SELECT, operatia de tip JOIN, pentru
a obtine numele si prenumele actorilor care joaca in filmul curent, film ce poate fi identificata
prin current_film_id.
SELECT current_title;
6
WHERE film_id = current_film_id;
Parasirea structurii iterative este asigurata prin verificarea variabilei done. In momentul in care
se identifica la nivelul variabilei done valoarea 1 este apelata structura de salt LEAVE, pentru a
permite parasirea buclei.
IF done = 1 THEN
LEAVE get_actors;
END IF;
Apelul procedurii stocate get_film_actors, care furnizeaza valoare ‘ALIEN’ pentru cautarea in
titlul filmelor, afiseaza pentru fiecare film care respecta expresia de cautare lista actorilor care
joaca in acel film.
+---------------+
| current_title |
+---------------+
| ALIEN CENTER |
+---------------+
1 row in set
+------------+-----------+
| first_name | last_name |
+------------+-----------+
7
| BURT | DUKAKIS |
| KENNETH | PALTROW |
| SIDNEY | CROWE |
| RENEE | TRACY |
| HUMPHREY | WILLIS |
| MENA | HOPPER |
+------------+-----------+
6 rows in set
+---------------+
| current_title |
+---------------+
| DESIRE ALIEN |
+---------------+
1 row in set
+------------+-----------+
| first_name | last_name |
8
+------------+-----------+
| TOM | MCKELLEN |
| JOHNNY | CAGE |
| ANGELINA | ASTAIRE |
| JULIANNE | DENCH |
| CATE | HARRIS |
| LAURA | BRODY |
| ROCK | DUKAKIS |
+------------+-----------+
7 rows in set
+---------------+
| current_title |
+---------------+
| HOBBIT ALIEN |
+---------------+
1 row in set
9
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| VIVIEN | BERGEN |
| ELVIS | MARX |
| DUSTIN | TAUTOU |
| WALTER | TORN |
| WARREN | JACKMAN |
| DARYL | CRAWFORD |
| LAURA | BRODY |
| REESE | WEST |
+------------+-----------+
8 rows in set
Cursorii și elementele de control al fluxului (flow control) sunt două concepte distincte în SQL și
au roluri diferite în manipularea datelor. Iată diferența între ele:
Cursorii:
10
Cursorii sunt utilizate pentru a itera prin seturile de rezultate ale unei interogări și a
manipula înregistrările în mod individual.
Cursorii permit parcurgerea înregistrărilor pe rând și accesarea valorilor acestora într-un
mod secvențial.
Acestea sunt adesea folosite pentru a realiza operații complexe și personalizate care
necesită logica individuală pe baza valorilor înregistrărilor.
Cursorii pot fi declarați, deschiși, iterați prin înregistrări și închiși folosind comenzi
specifice.
Elementele de control al fluxului în SQL, cum ar fi instrucțiunile IF, CASE, WHILE, LOOP,
permit controlul fluxului execuției codului în funcție de condiții și iterații.
Aceste elemente permit executarea secvențială sau repetată a unor blocuri de cod pe baza
unor condiții specifice.
Ele sunt utilizate pentru a controla fluxul de execuție într-un mod condițional și iterativ.
Elementele de control al fluxului nu sunt specifice cursorilor și pot fi folosite în diferite
contexte, inclusiv în afara manipulării seturilor de date.
Putem folosi oricare dintre cele trei constructe de buclă (bucla simplă, bucla WHILE și bucla REPEAT
UNTIL ) pentru a itera prin rândurile returnate de un cursor. În fiecare caz, trebuie să construim bucla
astfel încât bucla să se termine atunci când „variabila din ultimul rând” este setată de handlerul NOT
FOUND .
Exemplu:
Cea mai simplă construcție este secvența LOOP-LEAVE-END LOOP . În acest caz, bucla cursorului nostru
ar arăta ca cea prezentată în urmatorul exemplu:
OPEN dept_csr;
dept_loop1:LOOP
11
FETCH dept_csr INTO l_department_id,l_department_name,l_location;
IF no_more_departments=1 THEN
LEAVE dept_loop1;
END IF;
SET l_department_count=l_department_count+1;
END LOOP;
CLOSE dept_csr;
SET no_more_departments=0;
Logica din exemplul de mai sus este simplă: deschidem cursorul și apoi preluăm iterativ rândurile. Dacă
încercăm să aducem dincolo de sfârșitul setului de rezultate, handlerul setează no_more_departments
la 1 și apelăm instrucțiunea LEAVE pentru a termina bucla. În cele din urmă, închidem cursorul și
resetam variabila no_more_departments .
Bucla WHILE este foarte familiară programatorilor și, prin urmare, ar putea părea o alegere naturală
pentru construirea unei bucle de cursor. De fapt, totuși, veți găsi foarte probabil că bucla REPEAT UNTIL
este o construcție mai potrivită pentru o buclă de cursor. REPEAT își execută întotdeauna corpul cel
puțin o dată înainte de a evalua expresia de continuare . În contextul procesării cursorului, de obicei
vom dori să preluăm cel puțin o dată înainte de a verifica pentru a vedea dacă am terminat de procesat
setul de rezultate al cursorului. Prin urmare, utilizarea buclei REPEAT UNTIL poate produce un cod mai
lizibil, așa cum se arată în Exemplul 5-11.
SET no_more_departments=0;
OPEN dept_csr;
REPEAT
FETCH dept_csr INTO l_department_id,l_department_name,l_location;
UNTIL no_more_departments
END REPEAT;
CLOSE dept_csr;
SET no_more_departments=0;
Cu toate acestea, această buclă funcționează doar pentru că nu am făcut nimic cu fiecare rând preluat
de cursor. Preluarea rândurilor de pe un cursor doar pentru asta este foarte neobișnuită, este mult mai
obișnuit să faci ceva cu rândurile returnate. De exemplu, în primul nostru exemplu LOOP-LEAVE - END
LOOP , cel puțin am numărat rândurile returnate de cursor.
12
Cu toate acestea, deoarece preluarea finală nu returnează niciun rând, avem nevoie de o modalitate de
a evita procesarea după acea preluare finală.
Deci, de fapt, chiar dacă folosim bucla REPEAT UNTIL , tot avem nevoie de LEAVEinstrucțiune pentru a
evita procesarea rândului inexistent returnat (sau mai degrabă, nereturnat) de preluarea finală.
Astfel, dacă dorim să numărăm numărul de rânduri returnate de cursor (sau să facem orice altceva cu
rezultatele), va trebui să includem etichete de buclă și o instrucțiune LEAVE , ca în versiunea modificată
a exemplului nostru anterior, prezentat în Exemplul 5. -12.
SET no_more_departments=0;
OPEN dept_csr;
dept_loop:REPEAT
FETCH dept_csr INTO l_department_id,l_department_name,l_location;
IF no_more_departments THEN
LEAVEdept_loop;
END IF;
SET l_department_count=l_department_count+1;
UNTIL no_more_departments
END REPEAT dept_loop;
CLOSE dept_csr;
SET no_more_departments=0;
Necesitatea includerii unei instrucțiuni LEAVE în aproape fiecare buclă REPEAT UNTIL face ca prezența
clauzei UNTIL să fie redundantă, deși, fără îndoială, îmbunătățește lizibilitatea și vă protejează împotriva
posibilității unei bucle infinite dacă instrucțiunea LEAVE nu se execută (poate că ați codificat greșit
clauza IF ) .
În cele din urmă, bucle de cursor valide pot fi stabilite în orice mod și nu există niciun caz convingător
pentru a recomanda un stil față de celălalt.
Tot ce putem spune este că codul tău în ansamblu va fi mai ușor de citit dacă folosești un stil consecvent
pentru toate buclele cursorului.
O alternativă la o instrucțiune LEAVE ar fi o instrucțiune IF care execută orice post-procesare are loc
odată ce determinăm că FETCH a ajuns la sfârșitul setului de rezultate. Exemplul 5-13 arată cum am
13
putea construi această buclă pentru exemplul nostru. În acest caz, se adaugă o instrucțiune IF care
efectuează procesarea rândurilor numai dacă variabila no_more_departments nu a fost setată.
Exemplu de utilizarea unui bloc IF ca alternativă la o instrucțiune LEAVE într-o buclă de cursor REPEAT
UNTIL
SET no_more_departments=0;
OPEN dept_csr;
dept_loop:REPEAT
FETCH dept_csr INTO l_department_id,l_department_name,l_location;
IF no_more_departments=0 THEN
SET l_department_count=l_department_count+1;
END IF;
UNTIL no_more_departments
END REPEAT dept_loop;
CLOSE dept_csr;
SET no_more_departments=0;
WHILE își evaluează starea înainte de prima execuție a buclei, deci este o alegere mai puțin logică decât
REPEAT-UNTIL sau LOOP-END LOOP , deoarece în mod logic nu putem ști dacă am ajuns la sfârșitul
cursorului până nu obținem cel puțin unul. rând.
Pe de altă parte, WHILE este probabil construcția în buclă utilizată în cea mai mare varietate de alte
limbaje de programare, așa că ar putea conferi o înțelegere mai clară a intențiilor programului celor care
nu sunt familiarizați cu limbajul programului stocat MySQL.
În orice caz, bucla WHILE necesită, de asemenea, o instrucțiune LEAVE dacă există vreo prelucrare a
rezultatelor cursorului încercată în cadrul buclei, astfel încât codul din Exemplul 5-14 arată foarte
asemănător cu exemplele noastre anterioare.
14
FROM departments;
SET no_more_departments=0;
OPEN dept_csr;
dept_loop:WHILE(no_more_departments=0) DO
FETCH dept_csr INTO l_department_id,l_department_name,l_location;
IF no_more_departments=1 THEN
LEAVE dept_loop;
END IF;
SET l_department_count=l_department_count+1;
END WHILE dept_loop;
CLOSE dept_csr;
SET no_more_departments=0;
De exemplu, o buclă ar putea prelua o listă de clienți interesanți, în timp ce o buclă interioară preia toate
comenzile pentru acești clienți.
Cea mai importantă problemă legată de acest tip de imbricare este că variabila handler NOT FOUND va fi
setată ori de câte ori se finalizează oricare dintre cursori, așa că va trebui să fiți foarte atenți pentru a vă
asigura că o condiție NOT FOUND nu determină închiderea ambelor cursore.
15
OPEN dept_csr;
dept_loop: LOOP Loop through departments
FETCH dept_csr into l_department_id;
IF l_done=1 THEN
LEAVE dept_loop;
END IF;
OPEN emp_csr;
SET l_emp_count=0;
emp_loop: LOOP -- Loop through employee in dept.
FETCH emp_csr INTO l_employee_id;
IF l_done=1 THEN
LEAVE emp_loop;
END IF;
SET l_emp_count=l_emp_count+1;
END LOOP;
CLOSE emp_csr;
END;
Această procedură stocată conține o eroare subtilă. Când se finalizează prima buclă „internă” prin
cursorul emp_csr , valoarea lui l_done este setată la 1.
În consecință, la următoarea iterație prin bucla „exterioară” prin dept_csr , valoarea lui l_done este încă
setată la 1, iar cea exterioară bucla este terminată din greșeală.
Drept urmare, procesăm doar un singur departament. Există două soluții posibile la această problemă:
cea mai ușoară dintre cele două este pur și simplu să resetați variabila „negăsită” la sfârșitul fiecărei
bucle.
16
DECLARE l_employee_id INT;
DECLARE l_emp_count INT DEFAULT 0 ;
DECLARE l_done INT DEFAULT 0;
OPEN dept_csr;
dept_loop: LOOP -- Loop through departments
FETCH dept_csr into l_department_id;
IF l_done=1 THEN
LEAVE dept_loop;
END IF;
OPEN emp_csr;
SET l_emp_count=0;
emp_loop: LOOP -- Loop through employee in dept.
FETCH emp_csr INTO l_employee_id;
IF l_done=1 THEN
LEAVE emp_loop;
END IF;
SET l_emp_count=l_emp_count+1;
END LOOP;
CLOSE emp_csr;
SET l_done=0;
END;
Este întotdeauna o practică bună să resetați valoarea unei variabile „negăsit” odată ce aceasta a fost
utilizată, astfel încât iterațiile ulterioare ale cursorului să nu fie afectate.
Resetați întotdeauna variabila „negăsită” setată de un handler NOT FOUND după ce terminați o buclă de
cursor. Nerespectarea acestui lucru poate duce la terminarea prematură a buclelor de cursor ulterioare
sau imbricate.
17
O soluție ceva mai complexă, dar probabil mai robustă este să ofere fiecărui cursor propriul său handler.
Deoarece puteți avea un singur handler NOT FOUND activ în cadrul unui anumit bloc, acest lucru se
poate face doar prin încadrarea fiecărui cursor în propriul bloc. De exemplu, am putea plasa cursorul de
vânzări în propriul bloc cu propriul său handler NOT FOUND , ca în Exemplul 5-17.
sales_block: BEGIN
DECLARE l_last_sale INT DEFAULT 0;
Rețineți că acum avem o variabilă separată „negăsită” pentru fiecare cursor și am eliminat orice
posibilitate ca închiderea unui cursor să afecteze starea altuia.
Cu toate acestea, rețineți că resetăm în continuare variabilele „negăsit” după ce am finalizat fiecare
buclă de cursor, aceasta rămâne foarte recomandată, deoarece este posibil să doriți să redeschideți un
cursor în același bloc.
18
Nu presupuneți că puteți ieși din bucla cursorului numai când ultimul rând a fost preluat; puteți emite o
declarație LEAVE în orice moment în care credeți că procesarea dvs. a fost finalizată. Este posibil să
căutați doar una sau un număr limitat de înregistrări candidate în setul de rezultate sau este posibil să fi
detectat o altă condiție care sugerează că procesarea ulterioară nu este necesară.
De exemplu, dacă încercați să CLOSE or FETCH intr-un cursor care nu este deschis, veți întâlni o eroare.
END;
Încercarea de a deschide un cursor care este deja deschis are ca rezultat o eroare Cursor este deja
deschis.
19
END;
//
20