Structuri de control

In acest capitol se explica structurile de control ale unui program PL/SQL. Acestea sint expresiile conditionale, salturile si buclele.

Introducere in expresiile de control ale PL/SQL

PL/SQL ofera facilitati de control al executiei conditionat si neconditionat.
Aceste sint:
instructiuneaIF
Ofera controlul selectiv al actiunilor, bazat pe indeplinirea unor conditii.
instructiunea GOTO
Se foloseste la saltul neconditionat la o eticheta din program.
ciclul simplu
Asigura repetarea unor actiuni fara impunerea de conditii
ciclul FOR
Pentru control iterativ al actiunilor, bazat pe un contor.
cicluri WHILE
Pentru control iterativ al actiunilor, bazat pe indeplinrea unei conditii
instructiunea EXIT
Pentru a iesi dintr-un ciclu
Acolo unde aceste facilitati includ o conditie, aceasta poate fi orice expresie booleana valida in SQL, cu exceptia query imbricat. Aceasta include: Exemple:
        var <= var2
        var3 LIKE '%S%'
        var4 BETWEEN '01-JUN-92' AND '01-SEP-92'
        var2 IS NOT NULL
        bool_var
        x IN ( 'Y', 'N', 'YES', 'NO' )
        ( a = b OR c = 5 ) AND p < 400

Instructiunea IF

Instructiunea IF are o structura similara cu echivalentul din limbajele procedurale. Ea permite executarea unor actiuni selectiv, bazat pe indeplinirea unor conditii. Structura sa generala este prezentata mai jos. Sintaxa:
        IF conditie THEN actiuni [ ELSIF conditie THEN actiuni ]
                                                [ ELSE actiuni ]
        END IF;
unde "actiuni" pot fi una sau mai multe instructiuni PL/SQL sau SQL, fiecare terminata cu punct-virgula. Aceste "actiuni" pot include alte instructiuni IF cu aceiasi structura, ceea ce inseamna ca instruciunea poate contine IF, ELSE, ELSIF imbricate.

Instructiunea IF simpla

Sa incepem cu un exemplu care implica numai o conditie, fara nici una din clauzele optionale:
        IF v_ename = 'SCOTTY' THEN beam_me_up := 'YES';
                                   COMMIT;
        END IF;
Cele doua actiuni sind executate numai daca conditia este adevarata (TRUE). Daca conditia este falsa (FALSE sau NULL) atunci cele doua instructiuni sint excluse. In oricare din caz, executia continua cu urmatoarea instructiune din program de dupa END IF.

Notati ca daca constructia 'END IF' nu este prezenta la sfirsitul instructiunii, atunci PL/SQL ca cauta in instructiunile urmatoare cautind 'END IF'. Ca si in alte limbaje procedurale aceasta poate provoca afisarea unor mesaje de eroare confuze.

Urmatorul exemplu arata cum pot fi executate instructiuni alternative prin intermediul clauzei ELSE. Actiunile specificate prin ELSE sint executate daca conditiila este FALSE sau NULL, dupa care programul continua cu instructiunile ce urmeaza dupa END IF.

        IF ename = 'SCOTTY' THEN beam_me_up = 'YES' ;
                                 COMMIT;
                            ELSE beam_me_up = 'NO' ;
                                 ROLLBACK;
        END IF;

Desen cu instructiunea IF

Oricare set de actiuni poate include instructiuni IF inlantuite pentru a executa teste ulterioare inainte ca o actiune specifica sa fie executata. Fiecare instructiune IF inlantuita trebuie sa fie terminata de END IF-ul corespunzator.

Exemplu:

        IF deptno = 10 THEN accounting := 'YES';
                            IF job = 'MANAGER' THEN
                                INSERT INTO status VALUES ( ' Dept10 Manager');
                            END IF;
                            COMMIT;
                        ELSE accounting := 'NO';
                             IF job = 'MANAGER' THEN
                                INSERT INTO status VALUES (' Alt Mgr.');
                             END IF;
                             COMMIT;
        END IF;
diagrama 2 IF THEN ELSIF

Adesea, actiunile de executat in clauza ELSIF pot contine doar un alt IF. In aceasta situatie, este mult mai convenabil sa folositi clauza ELSIF, care elimina necesitatea de a inlantui END IF la sfirsitul fiecarui set ulterior de conditii/actiuni:

        IF f_title = 'doamna' THEN status := 'MARITATA';
                           ELSIF f_title = 'domnisoara' THEN
                                   status := 'NEM.'
                           ELSE   status:= 'NECUNOSCUTA';
        END IF;
Datorita posibilitatii de a imbrica actiuni, clauza ELSIF poate fi utilizata recursiv, daca doriti:
        IF choise = 1 THEN actiune := 'Run Payroll' ;
                      ELSIF choice = 2 THEN actiune := 'Run Accounts';
                      ELSIF choice = 3 THEN actiune := 'Backups';
                      ELSIF choice = 4 THEN actiune := 'Restore';
                      ELSE actiune := 'Invalid';
        END IF;

Cicluri de baza si instructiunea EXIT

Structura:

        LOOP
        -- actiuni de executat in
        -- cadrul ciclului
        END LOOP;

* EXIT termina ciclul

Sintaxa:

        EXIT [eticheta-ciclu] [WHEN conditie];

Cicluri si salturi in PL/SQL

PL/SQL ofera un numar de facilitati pentru cicluri si pentru a iesi din cicluri, cind se doreste, spre alta parte a programului. Cel mai simplu caz contine instructiunile ce se doresc repetate incluse intre delimitatorii LOOP si END LOOP:
        LOOP
        -- actiuni de executat
        -- in cadrul ciclului
        END LOOP;
De fiecare data cind programul ajunge la constructia END LOOP, controlul este redat expresiei LOOP corespunzatoare. Acest ciclu necontrolat va fi, bineinteles, infinit daca nu se folosesc instructiuni de control in interiorul ciclului, care sa provoace saltul in afara ciclului.

Instructiunea EXIT

Un ciclu poate fi terminat din interior, daca este utilizata instructiunea EXIT. EXIT permite transferul controlului instructiunii urmatoare de dupa END LOOP, permitind astfel incheierea imediata a ciclului.

Sintaxa:

        EXIT [loop-label] [WHEN conditie];
EXIT poate fi executat fie ca o actiune dintr-un IF, sau ca o instructiune individuala in cadrul unui ciclu. In acest caz, o clauza WHEN poate fi atasata la instructiunea IF, permitind terminarea conditionata a ciclului.

Exemplul 1:

        LOOP
          counter := counter + 1;
          INSERT INTO numbered_rows VALUES (counter);
          ...
          IF counter = 10 THEN COMMIT;
                               EXIT;
          END IF;
        END LOOP;

Exemplul 2:

        LOOP
          ...
          EXIT WHEN total_sals = 60000;
          ...
        END LOOP;

O alta posibilitate de a iesi dintr-un ciclu ar fi sa sariti la o instructiune etichetata in afara ciclului, folosind instructiunea GOTO. Aceasta este in general considerata ca o metoda mai putin structurata. GOTO si etichetele vor fi discutate mai tirziu, in acest capitol.

Utilizarea ciclurilor FOR pentru controlul iteratiilor

Ciclurile FOR au aceeasi structura generala ca si celee pe care deja le-ati vazut, dar adauga instructiuni de control la inceputul ciclului, care vor determina numarul de iteratii ce vor fi efectuate.

Sintaxa:

        FOR variabila_control IN [REVERSE] valoare_inferioare .. valoare_superioara

Unde 'variabila_control' este numele unei variabile intregi ale carei valori vor fi incrementate/decrementate automat la fiecare iteratie a ciclului. Aceasta variabila este creata de catre ciclu, si nu poate fi scrisa de nici o instructiune din cadrul ciclului. Durata de viata se termina la terminarea ciclului.

'valoare_inferioara' si 'valoare_superioar' sint expresii intregi, care determina valorile pe care le va lua variabila de control.

Implicit, variabila de control este initializata cu o valoare mica, si este incrementata cu +1 la fiecare iteratie pina cind valoarea superioara este atinsa.

Exemplu:

        FOR i IN 1 .. 2000
        LOOP
            INSEERT INTO numbered_rows VALUES (i);
            preserve_i := i;
            ...
        END LOOP;

Observati ca daca valoarea lui 'i' este necesara dupa terminarea ciclului, atunci ea trebuie copiata intr-o variabila declarata inainte de terminarea ciclului.

Ciclul FOR poate si sa decrementeze variabila de control, incepind de la valoarea superioara in prima iteratie:

        FOR n IN REVERSE 50 .. myvar+50
        LOOP
          -- n are valoarea myvar+50 la prima iteratie.
          -- si valoarea 50 la ultima
        END LOOP;

Utilizarea ciclurilor WHILE pentru controlul iteratiilor

Aceasta este o structura de control alternativa, care permite iteratiilor sa fie executate conditionat.

Sintaxa:

        WHILE conditie

Conditia este evaluata la inceputul fiecarei iteratii, si ciclul este terminat cind conditia este FALSE. Daca conditia este FALSE atunci cind ciclul incepe, atunci nici o interatie nu se executa.

Exemplul 1:

        WHILE bill <250 AND food LIKE '%taco%'
        LOOP
          -- actiuni
        END LOOP;

Exemplul 2:

        WHILE bool_var
        LOOP
          -- actiuni
        END LOOP;

Normal, daca variabila implicata in conditii nu se schimba in timpul executiei ciclului, atunci ciclul nu se va termina niciodata. Totusi instructiunea EXIT poate fi inclusa atit in ciclurile FOR si WHILE.

Controlul ciclurilor imbricate

Ciclurile pot fi imbricate pe nivele multiple. Puteti imbrica cicluri FOR in cicluri WHILE si invers. In mod normal, terminarea unui ciclu intern nu termina ciclul care il include, exceptind cazul cind este generata o exceptie.

Etichetele in PL/SQL sint definite astfel:

        << label-name >>

Numele etichetei urmeaza aceleasi reguli ca si orice identificatori. O eticheta este plasata inaintea unei instructiuni, fie pe aceasi linie, fie pe o linie separata.

Ciclurile pot fi etichetate prin plasatea unei etichete inainte de cuvintul LOOP, si dupa END LOOP, conform:

        << outer_limits >> LOOP
                             -- actiuni
                           END LOOP outer_limits;

Daca un ciclu subordonat executa un EXIT, atunci el poate specifica care ciclu sa fie incheiat prin specificarea etichetei.

Exemplu:

        << main >> LOOP
                ...
                LOOP
                ...
                EXIT main WHEN total_done = 'YES';
                          -- paraseste ambele cicluri
                EXIT WHEN inner_done = 'YES';
                          -- paraseste doar ciclul interior
        END LOOP main;

Folosirea GOTO si a etichetelor

Dupa cum ati vazut, etichetele pot fi utilizate pentru a identifica cicluri atunci cind apar in structuri inlantuite. Aceasta este si cazul blocurilor inlantuite. O eticheta poate fi plasata inaintea inceputului si dupa sfirsitul unui bloc PL/SQL, astfel incit identitatea sa declarate sa poate fi deosebita de celelalte blocuri din structura inlantuita. Sa luam exemplul urmator:

        DECLARE
           var1 NUMBER;
        BEGIN
           .
           DECLARE
              var1 NUMBER := 400;
           BEGIN
              .
              var1 := var1 + 1 ;  -- var1 din subbloc se mareste cu 1
              .
           END;
           .
        END;

Variabila var1 care a fost declarata in blocul exterior nu se "vede" in cadrul blocului interior, deoarece acesta a declarat o variabila cu acelasi nume. Toate referirile la 'var1' din sub-bloc vor fi deci folosite pentru variabila local declarata.

Totusi, daca blocurile sint etichetate, identificatorii pot fi referiti prefixati de numele blocului dorit. (Observatie: blocul exterior nu trebuie neaparat etichetat). Un exemplu:

        BEGIN
        <> DECLARE
                        var1 NUMBER;
                        .
                   BEGIN
                        .
                        <> DECLARE
                                var1 NUMBER := 400;
                              BEGIN
                                .
                                block1.var1 := block1.var + 1;
                                        -- variabila din block 1
                                        -- este incrementata
                               END block2;
                  END block1;
        END;

Asa cum s-a aratat, END poate include numele blocului care trebuie terminat.

Instructiunea GOTO

Aceasta instructiune ofera salturi neconditionate la o eticheta in cadrul unui program PL/SQL. Utilizarea instructiunii trebuie minimizata, deoarece folosirea excesiva poate duce la programe foarte dezorganizate.

Valabilitatea unei etichete este in intregul bloc in care a fost definita. GOTO poate, deci, transmite controlul catre puncte etichetate din blocul curent sau catre un punct etichetat dintr-un alt bloc care contine blocul curent.

Sintaxa:

        GOTO eticheta;

Unde 'eticheta' poate fi orice eticheta care marcheaza o pozitie in cadrul blocului curent sau al altuia ce il cuprinde. GOTO nu poate transmite controlul in interiorul unui sub-bloc.

Exercitii pentru capitolul 22

  1. Scrieti un bloc PL/SQL care sa insereze un rind in tabela MESSAGES cu coloana NUMCOL1 continind 1 daca este primul rind, 2 daca este al 2-lea s.a.m.d. Nu inserati rinduri etichetate 6 sau 8 si iesiti din ciclu cind valoarea 10 a fost inserata. Actualizati baza cind se termina ciclul. (atentie - COUNT este cuvint rezervat!)
  2. (daca aveti timp) Selectati coloanele ENAME, HIREDATE si SAL din tabela EMP unde EMPNO este egal cu un numar introdus in timpul executiei. Dupa ce ati selectat coloanele in variabile, inserai un rind in MESSAGES bazat pe unul din urmatoarele criterii:
    Criteriu / Mesaj
    Salariu mai mare de 1200
    Salariu mai mare de 1200
    ENAME contine 'T'
    Numele contine "T"
    HIREDATE este Decembrie
    Decembrie
    Nici unul din cazurile precedente
    ** Nimic **
    Testatti pentru angajatii cu numerele 7654, 7369, 7900, 7876.
  3. Urmatorul cod executa un ciclu, cu diferite valori pentru V la fiecare interatie (in domeniul 1 la 10).
            UPDATE messages SET numcol2 = 100
            WHERE numcol1 = V ;
    
    Daca rularea UPDATE produce altceva decit un singur rind, atunci se va iesi din ciclu.( Puteti testa atributul SQL%ROWCOUNT, asa cum este discutat in capitolul 23).

Solutii la capitolul 22

  1.         BEGIN
              FOR v_count IN 1..10
              LOOP
                    IF v_count NOT IN (6,8) THEN
                       INSERT INTO messages (numcol1) VALUES (v_count);
                    END IF;
              END LOOP;
              COMMIT;
            END;
    
  2.         DECLARE
              v_ename       emp.ename%TYPE;
              v_hiredate    emp.hiredate%TYPE;
              v_sal         emp.sal%TYPE;
              v_message     CHAR(30);
            BEGIN
              SELECT ename, hiredate, sal
              INTO v_ename, v_hiredate, v_sal
              FROM emp
              WHERE empno = &EMPLOYEE_NO ;
    
              IF v_sal > 1200 THEN
                    v_message := 'Salariu mai mare de 1200';
              ELSIF v_ename LIKE '%T%' THEN
                    v_message := 'Numele contine "T"';
              ELSIF TO_CHAR(v_hiredate, 'MON') = 'DEC' THEN
                    v_message := 'Decembrie';
              ELSE v_message := '** Nimic **';
              END IF;
    
              INSERT INTO messages (charcol1) values (v_message);
            END;
    
  3.         BEGIN
                    FOR v IN 1 .. 10
                    LOOP
                            UPDATE messages SET numcol2 = 100
                            WHERE numcol1 = v;
                            EXIT WHEN SQL%ROWCOUNT <> 1;
                    END LOOP;
            END;