Ciklusok

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:

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 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;
/