You are on page 1of 20

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 stochează temporar rezultatele interogării și permite programatorului să se deplaseze


prin ele.

Cursorul din MySQL este aproape identic cu un cursor din alte baze de date.

Cursorii sunt suportați numai în procedurile, funcțiile și declanșatoarele salvate

Cursorul MySQL este doar pentru citire, nu poate fi derulat și este “sensitive”.

Cursorii ofera un mecanism eficient de identificare a interogarilor si iterare a inregistrarilor


acestora.
In sistemele de tip MySQL, cursorii sunt structuri de tip read-only, adica nu ofera posibilitatea
actualizarii datelor din tabelele de baza prin intermediul lor.
Parcurgerea inregistrarilor de la nivelul unui cursor poate fi realizata doar in ordinea stabilita in
interogare, intr-o singura directie si fara salturi.
Din acest punct de vedere, cursorii sunt structuri de tip non-scrollable.
In afara acestor doua proprietati, read-only si non-scrollable, cursorii din cadrul sistemelor de
tip MySQL beneficiaza si de o a treia proprietate, sunt structuri de tip asensitive, care refera
datele de la nivelul tabelelor de baza si nu copii temporare ale acestor date.

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ă.

Un cursor este întotdeauna asociat cu o instrucțiune SELECT ;


Cursorii trebuie declarati inainte de utilizare. La nivelul declaratiei este stabilit un nume pentru
cursor, nume caruia i se asociaza o definitie a unei interogari. In cadrul acestui proces nu sunt
colectate date de la nivelul tabelelor referite.
DECLARE film_cursor CURSOR FOR

SELECT film_id, title

FROM film

WHERE title LIKE CONCAT('%', film_title, '%');

In exemplul precedent, poate fi observata declaratia cursorului film_cursor, dar si interogarea


asociata acestuia. Interogarea are in vedere extragerea valorilor corespunzatoare coloanelor
film_id si title ale inregistrarilor din tabelul film al bazei de date sakila pentru care titlul contine
valoarea transmisa prin intermediul variabilei film_title.

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:

CREATE PROCEDURE bad_cursor( )


BEGIN
DECLARE c CURSOR FOR SELECT * from departments;

2
DECLARE i INT;
END;

ERROR 1337 (42000): Variable or condition declaration after cursor or handler


declaration

Un cursor poate face referire la variabilele programului stocate în cadrul clauzei WHERE sau


(mai puțin frecvent) a listei de coloane.

CREATE PROCEDURE cursor_demo (in_customer_id INT)


BEGIN
DECLARE v_customer_id INT;
DECLARE v_customer_name VARCHAR(30);
DECLARE c1 CURSOR FOR
SELECT in_customer_id,customer_name
FROM customers
WHERE customer_id=in_customer_id;

Deschiderea si inchiderea cursorilor

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;

Deschiderea cursorului implica executia interogarii asociate acestuia si obtinerea inregistrarilor


din rezultat. Pentru cursorul declarat anterior, operatia de deschidere presupune precizarea
numelui cursorului, film_cursor, intr-o instructiune OPEN.

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:

FETCH [[NEXT] FROM] nume_cursor INTO nume_variabila [, nume_variabila] ...

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:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

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.

DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_last_row_fetched=1;

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;

Rețineți că nu trebuie să procesăm toate rândurile din setul de rezultate; putem emite


instrucțiunea LEAVE în orice moment pentru a încheia bucla cursorului dacă am procesat toate
datele de care avem nevoie.

4
Reteta cursor

1. Declararea unui cursor:

DECLARE cursor_name CURSOR FOR select_statement

2. Deschiderea cursorului:

OPEN cursor_name

3. Obţinerea valorilor:

FETCH [[NEXT] FROM] cursor_name INTO variable_name [, variable_name] ...

4. Închiderea cursorului:

CLOSE cursor_name

Exemplu

DECLARE done INTEGER DEFAULT 0;

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.

FETCH film_cursor INTO current_film_id, 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;

SELECT first_name, last_name

FROM actor INNER JOIN film_actor USING(actor_id)

INNER JOIN film USING(film_id)

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.

mysql> CALL sakila.get_film_actors('ALIEN');

+---------------+

| 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

Query OK, 0 rows affected

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 (flow control):

 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.

Tipuri de bucle de cursor

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:

DECLARE dept_csr CURSOR FOR


SELECT department_id,department_name, location
FROM departments;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_departments=1;

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:

Exemplu cu o buclă de cursor LOOP-LEAVE-END LOOP

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.

Exemplu de bucla cursorului cu bucla REPEAT UNTIL

DECLARE dept_csr CURSOR FOR


SELECT department_id,department_name, location
FROM departments;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_departments=1;

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.

Exemplu bucla REPEAT UNTIL au nevoie și de o instrucțiune LEAVE

DECLARE dept_csr CURSOR FOR


SELECT department_id,department_name, location
FROM departments;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_departments=1;

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

DECLARE dept_csr CURSOR FOR


SELECT department_id,department_name, location
FROM departments;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_departments=1;

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;

Al treilea stil de buclă de cursor implică bucla WHILE-END WHILE .

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.

Exemplu un cursor buclă WHILE

DECLARE dept_csr CURSOR FOR


SELECT department_id,department_name, location

14
FROM departments;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_departments=1;

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;

Bucle de cursor imbricate (Nested Cursor Loops)

Nu este neobișnuit să imbricați buclele cursorului.

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.

Exemplu O buclă de cursor imbricată (defectuoasă).

CREATE PROCEDURE bad_nested_cursors( )


READS SQL DATA
BEGIN

DECLARE l_department_id INT;


DECLARE l_employee_id INT;
DECLARE l_emp_count INT DEFAULT 0 ;
DECLARE l_done INT DEFAULT 0;

DECLARE dept_csr cursor FOR


SELECT department_id FROM departments;

DECLARE emp_csr cursor FOR


SELECT employee_id FROM employees
WHERE department_id=l_department_id;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_done=1;

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;

SELECT CONCAT('Department ',l_department_id,' has ',


l_emp_count,' employees');

END LOOP dept_loop;


CLOSE dept_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.

Exemplu corect de cursor imbricat

CREATE PROCEDURE good_nested_cursors1( )


READS SQL DATA
BEGIN

DECLARE l_department_id INT;

16
DECLARE l_employee_id INT;
DECLARE l_emp_count INT DEFAULT 0 ;
DECLARE l_done INT DEFAULT 0;

DECLARE dept_csr cursor FOR


SELECT department_id FROM departments;
DECLARE emp_csr cursor FOR
SELECT employee_id FROM employees
WHERE department_id=l_department_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_done=1;

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;

SELECT CONCAT('Department ',l_department_id,' has ',


l_emp_count,' employees');

END LOOP dept_loop;


CLOSE dept_csr;

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.

Exemplu de cursoare imbricate folosind blocuri imbricate

DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_last_customer=1;


SET l_last_customer=0;
OPEN customer_csr;
cust_loop:LOOP /* Loop through overdue customers*/

FETCH customer_csr INTO l_customer_id;


IF l_last_customer=1 THEN LEAVE cust_loop; END IF; /*no more rows*/
SET l_customer_count=l_customer_count+1;

sales_block: BEGIN
DECLARE l_last_sale INT DEFAULT 0;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_last_sale=1;


OPEN sales_csr;
sales_loop:LOOP /* Get all sales for the customer */

FETCH sales_csr INTO l_sales_id;


IF l_last_sale=1 THEN LEAVE sales_loop; END IF; /*no more rows*/

CALL check_sale(l_sales_id); /* Check the sale status */


SET l_sales_count=l_sales_count+1;

END LOOP sales_loop;


SET l_last_sale=0;
CLOSE sales_csr;
END sales_block;

END LOOP cust_loop;


SET l_last_customer=0;
CLOSE customer_csr;

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.

Ieșirea prematură din bucla cursorului

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ă.

Condiții de eroare a cursorului

Instrucțiunile cursorului trebuie să apară în secvența OPEN-FETCH-CLOSE . Orice variație a acestei


secvențe va duce la erori de rulare.

De exemplu, dacă încercați să CLOSE or FETCH intr-un cursor care nu este deschis, veți întâlni o eroare.

Exemplu de eroare cursorul nu este deschis

mysql> CREATE PROCEDURE csr_error2( )


BEGIN
DECLARE x INT DEFAULT 0;
DECLARE c cursor for select 1 from departments;
CLOSE c;

END;

Query OK, 0 rows affected (0.00 sec)


mysql> CALL csr_error2( );

ERROR 1326 (24000): Cursor is not open

Încercarea de a deschide un cursor care este deja deschis are ca rezultat o eroare Cursor este deja
deschis.

Exemplu de eroare cursorul este deja o eroare deschisă

mysql> CREATE PROCEDURE csr_error3( )


BEGIN
DECLARE x INT DEFAULT 0;
DECLARE c cursor for select 1 from departments;
OPEN c;
OPEN c;

19
END;
//

Query OK, 0 rows affected (0.00 sec)


mysql> CALL csr_error3( );

ERROR 1325 (24000): Cursor is already open

20

You might also like