A ciklusok olyan programeszközök, amelyek egy adott tevékenység tetszés szerinti (adott esetben akár nullaszoros) ismétlését teszik lehetővé. Az ismétlődő tevékenységet egy végrehajtható utasítássorozat írja le, ezt az utasítássorozatot a ciklus magjának nevezzük.
A PL/SQL négyfajta ciklust ismer, ezek a következők:
alapciklus (vagy végtelen ciklus);
WHILE ciklus (vagy előfeltételes ciklus);
FOR ciklus (vagy előírt lépésszámú ciklus);
kurzor FOR ciklus.
A negyedik fajta ciklust a 8. fejezetben tárgyaljuk.
A ciklusmag ismétlődésére vonatkozó információkat (amennyiben vannak) a mag előtt, a ciklus fejében kell megadni. Ezek az információk az adott ciklusfajtára nézve egyediek. Egy ciklus a működését mindig csak a mag első utasításának végrehajtásával kezdheti meg. Egy ciklus befejeződhet, ha
az ismétlődésre vonatkozó információk ezt kényszerítik ki;
a GOTO utasítással kilépünk a magból;
az EXIT utasítással befejeztetjük a ciklust;
kivétel (lásd 7. fejezet) váltódik ki.
Az alapciklus alakja a következő:
[címke] LOOP utasítás [utasítás]…
END LOOP [címke];
Az alapciklusnál nem adunk információt az ismétlődésre vonatkozóan, tehát ha a magban nem fejeztetjük be a másik három utasítás valamelyikével, akkor végtelenszer ismétel.
Példa
SET SERVEROUTPUT ON SIZE 10000;
/* Loop 1 - egy végtelen ciklus */
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE('Ez egy végtelen ciklus.');
-- A DBMS_OUTPUT puffere szab csak határt ennek a ciklusnak.
END LOOP;
END;
/
/* Loop 2 - egy látszólag végtelen ciklus */
DECLARE
v_Faktorialis NUMBER(5);
i PLS_INTEGER;
BEGIN
i := 1;
v_Faktorialis := 1;
LOOP
-- Előbb-utóbb nem fér el a szorzat 5 számjegyen.
v_Faktorialis := v_Faktorialis * i;
i := i + 1;
END LOOP;
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE(v_Faktorialis || ' a legnagyobb, legfeljebb 5-jegyű faktoriális.');
END;
/
A WHILE ciklus alakja a következő:
[címke] WHILE feltétel LOOP
utasítás [utasítás] …
END LOOP [címke];
Ennél a ciklusfajtánál az ismétlődést egy feltétel szabályozza. A ciklus működése azzal kezdődik, hogy kiértékelődik a feltétel. Ha értéke igaz, akkor lefut a mag, majd újra kiértékelődik a feltétel. Ha a feltétel értéke hamis vagy NULL lesz, akkor az ismétlődés befejeződik, és a program folytatódik a ciklust követő utasításon.
A WHILE ciklus működésének két szélsőséges esete van. Ha a feltétel a legelső esetben hamis vagy NULL, akkor a ciklusmag egyszer sem fut le (üres ciklus). Ha a feltétel a legelső esetben igaz és a magban nem történik valami olyan, amely ezt az értéket megváltoztatná, akkor az ismétlődés nem fejeződik be (végtelen ciklus).
Példák
/* While 1 - eredménye megegyezik Loop 2 eredményével. Nincs kivétel. */
DECLARE
v_Faktorialis NUMBER(5);
i PLS_INTEGER;
BEGIN
i := 1;
v_Faktorialis := 1;
WHILE v_Faktorialis * i < 10**5 LOOP
v_Faktorialis := v_Faktorialis * i;
i := i + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_Faktorialis || ' a legnagyobb, legfeljebb 5-jegyű faktoriális.');
END;
/
/* While 2 - üres és végtelen ciklus. */
DECLARE
i PLS_INTEGER;
BEGIN
i := 1;
/* Üres ciklus */
WHILE i < 1 LOOP
i := i + 1;
END LOOP;
/* Végtelen ciklus */
WHILE i < 60 LOOP
i := (i + 1) MOD 60;
END LOOP;
END;
/
Feltételezve, hogy a magjuk azonos, a következő két ciklus működése ekvivalens:
WHILE TRUE LOOP |
LOOP |
|
⋮ | ⋮ | |
END LOOP; |
END LOOP; |
Ezen ciklusfajta egy egész tartomány minden egyes értékére lefut egyszer. Alakja:
[címke] FOR ciklusváltozó IN [REVERSE] alsó_határ..felső_határ
LOOP utasítás [utasítás]...
END LOOP [címke];
A ciklusváltozó (ciklusindex, ciklusszámláló) implicit módon PLS_INTEGER típusúnak deklarált változó, amelynek hatásköre a ciklusmag. Ez a változó rendre felveszi az alsó_határ és felső_határ által meghatározott egész tartomány minden értékét és minden egyes értékére egyszer lefut a mag. Az alsó_határ és felső_határ egész értékű kifejezés lehet. A kifejezések egyszer, a ciklus működésének megkezdése előtt értékelődnek ki.
A REVERSE kulcsszó megadása esetén a ciklusváltozó a tartomány értékeit csökkenően, annak hiányában növekvően veszi fel. Megjegyzendő, hogy REVERSE megadása esetén is a tartománynak az alsó határát kell először megadni:
-- Ciklus 10-től 1-ig
FOR i IN REVERSE 1..10 LOOP …
A ciklusváltozónak a ciklus magjában nem lehet értéket adni, csak az aktuális értékét lehet felhasználni kifejezésben.
Ha az alsó_határ nagyobb, mint a felső_határ, a ciklus egyszer sem fut le (üres ciklus). A FOR ciklus nem lehet végtelen ciklus.
Példák
/* For 1 - Egy egyszerű FOR ciklus */
DECLARE
v_Osszeg PLS_INTEGER;
BEGIN
v_Osszeg := 0;
FOR i IN 1..100 LOOP
v_Osszeg := v_Osszeg + i;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Gauss már gyerekkorában könnyen kiszámolta, hogy');
DBMS_OUTPUT.PUT_LINE(' 1 + 2 + ... + 100 = ' || v_Osszeg || '.');
END;
/
/* For 2 - REVERSE használata */
BEGIN
DBMS_OUTPUT.PUT_LINE('Gauss egymás alá írta a számokat (most csak 1-10):');
FOR i IN 1..10 LOOP
DBMS_OUTPUT.PUT(RPAD(i, 4));
END LOOP;
DBMS_OUTPUT.NEW_LINE;
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT(RPAD(i, 4));
END LOOP;
DBMS_OUTPUT.NEW_LINE;
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT(RPAD('--', 4));
END LOOP;
DBMS_OUTPUT.NEW_LINE;
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT(RPAD(11, 4));
END LOOP;
DBMS_OUTPUT.NEW_LINE;
END;
/
/* For 3 - "FOR Elise" - Egy kis esettanulmány */
<<cimke>>
DECLARE
i VARCHAR2(10) := 'Almafa';
j INTEGER;
BEGIN
/* A példát úgy próbálja ki, hogy j-re nézve mindig csak egy értékadás történjen.
Ekkor igaz lesz az értékadás előtti megjegyzés. */
-- A határok egyike sem lehet NULL, a ciklusfejben j kiértékelése
-- VALUE_ERROR kivételt eredményez.
-- Az alsó határ nagyobb a felső határnál, üres a tartomány,
-- a ciklusmag nem fut le egyszer sem.
j := 0;
-- Az alsó es a felső határ megegyezik, a ciklusmag egyszer fut le.
j := 1;
-- Az alsó határ kisebb a felső határnál,
j := -1;
-- a ciklusmag jelen esetben j-1+1 = 10-szer fut le.
j := 10;
-- Túl nagy szám. A PLS_INTEGER tartományába nem fér bele,
-- így kivételt kapunk a ciklusfejben
j := 2**32;
-- Az i ciklusváltozó implicit deklarációja elfedi
-- az i explicit deklarációját
FOR i IN 1..j
LOOP
-- a ciklusváltozó szerepelhet kifejezésben
DBMS_OUTPUT.PUT_LINE(i*2);
-- hibás, a ciklusváltozó nevesített konstansként viselkedik
i := i + 1;
-- Az elfedett változóra minősítéssel hivatkozhatunk.
DBMS_OUTPUT.PUT_LINE(cimke.i);
cimke.i := 'Körtefa'; -- szabályos, ez a blokk elején deklarált i
END LOOP;
-- Az explicit módon deklarált i újra elérhető minősítés nélkül
DBMS_OUTPUT.PUT_LINE(i);
END;
/
Az EXIT utasítás bármely ciklus magjában kiadható, de ciklusmagon kívül nem használható. Hatására a ciklus befejezi a működését. Alakja:
EXIT [címke] [WHEN feltétel];
Az EXIT hatására a ciklus működése befejeződik és a program a követő utasításon folytatódik.
1. példa
/* Exit 1 - az EXIT hatására a ciklus befejeződik. */
DECLARE
v_Osszeg PLS_INTEGER;
i PLS_INTEGER;
BEGIN
i := 1;
LOOP
v_Osszeg := v_Osszeg + i;
IF v_Osszeg >= 100 THEN
EXIT; -- elértük a célt.
END IF;
i := i+1;
END LOOP;
DBMS_OUTPUT.PUT_LINE(i || ' az első olyan n szám, melyre 1 + 2 + … + n >= 100.');
END;
/
Az EXIT címke utasítással az egymásba skatulyázott ciklusok sorozatát fejeztetjük be a megadott címkéjű ciklussal bezárólag.
2. példa
/* Exit 2 - A címke segítségével nem csak a belső ciklus fejezhető be */
DECLARE
v_Osszeg PLS_INTEGER := 0;
BEGIN
<<kulso>>
FOR i IN 1..100 LOOP
FOR j IN 1..i LOOP
v_Osszeg := v_Osszeg + i;
-- Ha elértük a keresett összeget, mindkét ciklusból kilépünk.
EXIT kulso WHEN v_Osszeg > 100;
END LOOP;
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_Osszeg);
END;
/
A WHEN utasításrész a ciklus feltételes befejeződését eredményezi. Az ismétlődés csak a feltétel igaz értéke mellett nem folytatódik tovább.
3. példa
/* Exit 3 - EXIT WHEN - IF - GOTO */
DECLARE
v_Osszeg PLS_INTEGER;
i PLS_INTEGER;
BEGIN
i := 1;
v_Osszeg := 0;
LOOP
v_Osszeg := v_Osszeg + i;
-- 1.
EXIT WHEN v_Osszeg >= 100;
-- 2.
IF v_Osszeg >= 100 THEN
EXIT;
END IF;
-- 3.
IF v_Osszeg >= 100 THEN
GOTO ki;
END IF;
i := i+1;
END LOOP;
<<ki>>
DBMS_OUTPUT.PUT_LINE(i || ' az első olyan n szám, melyre 1 + 2 + … + n >= 100.');
END;
/