Wykład 5

Programowanie aplikacji baz danych 
po stronie serwera - PL/SQL

 

Streszczenie

Na kolejnych dwóch wykładach zostanie przedstawione podstawowe narzędzie używane w Oracle przy tworzeniu oprogramowania aplikacji na serwerze, mianowicie rozszerzenie języka SQL o elementy języka programowania – język PL/SQL. Podobny język jest określony w nowym Standardzie SQL:1999. Nosi on tam nazwę SQL/PSM.

W tym wykładzie omówimy bloki PL/SQL, które stanowią podstawowe składniki kodu używanego w procedurach, funkcjach, pakietach i wyzwalaczach (będące tematami następnego wykładu).
 


5.1 Blok PL/SQL

Język PL/SQL jest proceduralnym rozszerzeniem języka SQL i stanowi podstawę do pisania aplikacji Oracle – jest mianowicie dostępny w różnych programach narzędziowych ORACLE, jak np. SQL*Plus, Oracle*Forms czy Oracle*Reports, prekompilatory, procedury, wyzwalacze, aplikacje internetowe.
 Odpowiednikiem konstrukcji bloku (i programu) występujących w językach programowania jest w PL/SQL blok PL/SQL nazywany też blokiem anonimowym. Ma on następującą składnię:
 
DECLARE
   deklaracje obiektów PL/SQL jak zmienne, stałe, wyjątki, procedury, funkcje
BEGIN
   ciąg instrukcji do wykonania
EXCEPTION
   obsługa wyjątków (błędów)
END;
 
Deklaracje i obsługa wyjątków są opcjonalne. Bloki mogą być zagnieżdżone. Jedynymi instrukcjami języka SQL, które mogą się pojawić w bloku PL/SQL, są instrukcje SELECT, INSERT, UPDATE, DELETE, COMMIT i ROLLBACK.

Wprowadzając blok PL/SQL przy użyciu programu SQL*Plus, należy:

Wykonanie instrukcji w buforze bieżącej instrukcji (instrukcji SQL lub bloku PL/SQL) realizujemy poprzez podanie znaku / na osobnej linii. Instrukcje samego SQL*Plus nie są wpisywane do bufora.

Przykład

W poniższym bloku jest oprogramowana transakcja sprzedaży samochodu marki Fiat. Gdy w magazynie jest brak informacji o takiej marce samochodu, podnoszony jest błąd, który jest następnie obsługiwany w sekcji wyjątków poprzez wpisanie odpowiedniej informacji do tablicy dziennika błędów.

DECLARE
  ilość NUMBER(5);
BEGIN
  SELECT m.Stan INTO ilość FROM Magazyn m
  WHERE m.Produkt = 'Fiat';
  /* Gdy w kolumnie Produkt tabeli Magazyn nie ma wartości 'Fiat' jest podnoszony wyjątek o nazwie no_data_found. */
  IF ilość> 0 THEN
    UPDATE Magazyn SET Stan = Stan - 1
    WHERE Produkt = 'Fiat';
    INSERT INTO Zakupy
    VALUES ('Kupiono Fiata', Sysdate);
  ELSE
    INSERT INTO Zakupy
    VALUES ('Brak Fiatów', Sysdate);
  END IF;
  COMMIT;
EXCEPTION -- Początek sekcji wyjątków
WHEN no_data_found THEN
   INSERT INTO dziennik_błędów
   VALUES ('Nie znaleziono produktu FIAT');
END;
/
Proszę zwrócić uwagę na zamieszczanie komentarzy w kodzie:

Deklaracje zmiennych i stałych

Deklaracja zmiennej ma następującą postać:
 
identyfikator typ_danych [NOT NULL] [ := wyrażenie];
 
Opcjonalna część := wyrażenie umożliwia inicjalizację wartości zmiennej – w przeciwnym razie zmiennej jest przypisywane NULL

Deklaracja stałej ma następującą postać:
 
identyfikator CONSTANT typ_danych [NOT NULL] [ := wyrażenie];

Oto przykłady:

zarobki NUMBER(7,2);

pi CONSTANT NUMBER(7,5) := 3.14159;

nazwisko VARCHAR2(25) := 'Kowalski';

data DATE := Sysdate;

żonaty BOOLEAN := False;

liczba_dzieci BINARY_INTEGER :=0;

Do inicjalizacji wartości zmiennej zamiast operatora := można użyć klauzuli DEFAULT – używa się jej, gdy zmienna ma charakterystyczną wartość, np.
Data DATE DEFAULT Sysdate;
Zauważmy, że deklaracji zmiennych tego samego typu nie można łączyć razem jak w innych językach!

W PL/SQL są dostępne typy danych z języka SQL, a ponadto kilka innych, z których najważniejsze to typ BOOLEAN wartości logicznych i typ BINARY_INTEGER liczb całkowitych – niezależny od podtypów typu NUMBER i przez to wymagający przy zapisie mniej pamięci.

Nie należy nadawać tej samej nazwy zmiennej co kolumnie w tabeli. Zmienne i stałe PL/SQL mogą występować w instrukcjach SQL, tak jak stałe SQL i kolumny.
 

Wprowadzanie danych z klawiatury i wypisywanie wyników na ekran

Oprócz zmiennych deklarowanych w bloku PL/SQL mogą występować jeszcze zmienne z aplikacji korzystającej z bloku PL/SQL - poprzedza się je dwukropkiem (:zmienna) i nazywa się zmiennymi wiązania. Mogą również występować zmienne podstawienia SQL*Plus (&zmienna) - ale tylko w wyrażeniach i nigdy po lewej stronie w instrukcji przypisania.

Przy testowaniu i uruchamianiu aplikacji składających się z kodu PL/SQL wygodnie jest pobierać dane do testowania z klawiatury i wypisywać informacje o przebiegu obliczeń na ekran. Realizuje się to w następujący sposób. Po ustawieniu w SQL*Plus:

SET SERVEROUTPUT ON
w bloku PL/SQL może wystąpić instrukcja:
DBMS_OUTPUT.Put_line(wyrażenie_napisowe);
Oto skrypt SQL*Plus obejmujący wprowadzanie przez użytkownika wartości do bloku PL/SQL - za pomocą zmiennej podstawienia rocz_zarob, a następnie wypisywanie wyniku na ekran - za pomocą procedury DBMS_OUTPUT.Put_line. Należy pamiętać, ze w celu uruchomienia skryptu należy go najpierw zapisać w pliku, a następnie wywołać go w SQL*Plus przy użyciu instrukcji start.
SET SERVEROUTPUT ON
ACCEPT rocz_zarob PROMPT 'Podaj roczne zarobki: '
DECLARE
mies NUMBER(9,2) := &rocz_zarob;
BEGIN
  mies := mies/12;
  DBMS_OUTPUT.PUT_LINE ('Miesięczne zarobki = ' ||mies);
END;
/

Nie możemy użyć tego skryptu w iSQL*Plus, bo nie obsługuje on instrukcji ACCEPT. Na szczęście opuszczenie instrukci ACCEPT też prowadzi do satysfakcjonującego rezultatu. Dlaczego?

Zamiast zmiennych podstawienia można użyć zmiennych wiązania z SQL*Plus, np.

ACCEPT rocz_zarob PROMPT 'Podaj roczne zarobki: '
VARIABLE mies NUMBER
BEGIN
  :mies := &rocz_zarob/12;
END;
/
PRINT Mies

Zmienne systemowe

Jest pewna liczba zmiennych zadeklarowanych w systemie, z których można korzystać w kodzie PL/SQL (ale nie w SQL) – ich wartości dotyczą ostatnio wykonanej instrukcji SQL.

Obu zmiennych SQLERRM i SQLCODE można używać tylko w sekcji EXCEPTION.

Przykład użycia zmiennej SQL%ROWCOUNT (do obliczenia liczby usuwanych działów o numerze 50) przedstawia następujący blok:

DECLARE usunięte NUMBER;
BEGIN
  DELETE FROM Dept WHERE Deptno = 50;
  usunięte := SQL%ROWCOUNT;
  INSERT INTO dziennik VALUES ('Dział', usunięte, Sysdate);
END;
/

5.2 Instrukcja SELECT w PL/SQL

Wewnątrz bloku PL/SQL instrukcja SELECT nie wypisuje wyników na ekran i w zasadzie nie można jej użyć jako takiej bez dodatkowych konstrukcji.

Instrukcja SELECT ma w PL/SQL swoją specjalną postać. Wynik zapytania nie jest wyświetlany na ekranie, tylko zostaje zapisany na zmiennych - zamieszczanych w klauzuli INTO, która jest wymaganą klauzulą instrukcji SELECT w PL/SQL. Oto składnia instrukcji SELECT w PL/SQL:
 
SELECT wyrażenie, wyrażenie,...
INTO zmienna, zmienna,...
FROM tabela, tabela,...
[WHERE ...][GROUP BY ...][HAVING ...][FOR UPDATE OF ...];
 
Aby instrukcja była poprawna, instrukcja SELECT musi zwracać dokładnie jeden wiersz wyników.

Przykład

SELECT e.Ename
INTO nazwisko
FROM Emp e
WHERE e.Empno = 1030;
Wartości, na których operujemy w bloku PL/SQL, pochodzą na ogół z bazy danych, gdzie został określony ich typ danych. W związku z tym, wygodnie jest w bloku PL/SQL określać typ danych przez odniesienie do kolumny w bazie danych jako ''typ danych wymienionej kolumny'' np.
nazwisko Emp.Ename%TYPE

Rekordy odpowiadające wierszom z tabel

W PL/SQL jest możliwość użycia zmiennych "wierszowych", na których można zapamiętać cały wiersz pochodzący z tabeli. Dostęp do poszczególnych pól wiersza odbywa się tak, jak w przypadku rekordów, przez kropkę i podanie nazwy kolumny.

DECLARE
  rek_osob Emp%ROWTYPE; /* Typ wierszowy */
BEGIN
  SELECT * INTO rek_osob FROM Emp e WHERE e.Ename = 'SCOTT';
  rek_osob.Sal := 1.1*rek_osob.Sal;
  INSERT INTO Dziennik VALUES (rek_osob.Ename, rek_osob.Job, rek_osob.Sal, SYSDATE);
END;
/
Zmiennej wierszowej nie można użyć bezpośrednio po słowie kluczowym VALUES w instrukcji INSERT INTO, to znaczy trzeba między nawiasami dokładnie wyspecyfikować wstawiane wartości jedna po drugiej.

Rekordy PL/SQL

Rozszerzeniem typu wierszowego jest typ rekordowy – definiowany explicite ze swoimi polami w sekcji deklaracji. Ma on podobne własności i ograniczenia co typ wierszowy. Najpierw należy zdefiniować typ rekordowy, a następnie zadeklarować zmienne tego typu.

Przykład

Zdefiniujmy typ rekordu pracownika.

TYPE Typ_rek_prac IS RECORD
    (numer_prac NUMBER(4) NOT NULL,
     nazwisko VARCHAR2(40) NOT NULL,
     Zarobki NUMBER(8,2),
     Num_działu NUMBER(4));
rekord_prac Typ_rek_prac;
Typy rekordowe mogą być zagnieżdżone. Dostęp do pól rekordu jest za pomocą notacji kropkowej. Na zmienną rekordową można przyporządkowywać wartość innej zmiennej rekordowej, ale tylko tego samego typu rekordowego. Można ich używać w klauzuli INTO, tak jak zmiennych wierszowych:
SELECT *
 INTO rekord_prac
FROM Pracownicy p
WHERE p.Id_prac = 12;
Nie można ani porównywać wartości typu rekordowego ani nie można zmiennej rekordowej użyć bezpośrednio po słowie kluczowym VALUES w instrukcji INSERT.

5.3 Obsługa wyjątków

Ważnym elementem każdej profesjonalnej aplikacji jest obsługa błędów (wyjątków).
W PL/SQL standardowe błędy (wyjątki) mają przyporządkowane nazwy. Oto najczęściej używane nazwy błędów (wyjątków):
 
  • dup_val_on_index - powtórzenie tej samej wartości w indeksie jednoznacznym,

  • no_data_found - instrukcja SELECT nie zwróciła wartości dla zmiennych w klauzuli INTO,

  • too_many_rows - instrukcja SELECT zwróciła więcej niż jeden wiersz wartości dla zmiennych w klauzuli INTO,

  • zero_divide - dzielenie przez zero,

  • timeout_on_resource - zbyt długie oczekiwanie na zasoby,

  • invalid_cursor - niepoprawna operacja na kursorze,

  • login_denied - niepoprawna nazwa użytkownika/hasło,

  • invalid_number - niepoprawna konwersja na liczbę,

  • storage_error - brak pamięci,

  • value_error - błąd związany z działaniem na wartościach,

  • cursor_already_open - kursor już został otwarty,

  • program_error - błąd w interpreterze PL/SQL.

 
Oto przykład, w którym po wykonaniu instrukcji SELECT, występuje przykładowa obsługa możliwych do wystąpienia błędów (wyjątków).

DECLARE
  nazwisko Emp.Ename%TYPE;
  stanowisko Emp.Job%TYPE;
  komunikat VARCHAR2(100);
BEGIN
  SELECT e.Ename, e.Job
  INTO nazwisko, stanowisko
  FROM Emp e
  WHERE e.Hiredate BETWEEN '01-JAN-93' AND '01-JAN-94';
EXCEPTION
WHEN no_data_found THEN
  INSERT INTO Dziennik 
  VALUES ('0 zatrudnionych w 93');
  DBMS_OUTPUT.Put_line('0 zatrudnionych w 93');
WHEN too_many_rows THEN
  INSERT INTO Dziennik VALUES (''Więcej niż 1 w 93');
  DBMS_OUTPUT.Put_line('Więcej niż 1 w 93');
WHEN OTHERS THEN -- Obsługa pozostałych błędów
  komunikat := 'Błąd nr.= ' || SQLCODE|| ',komunikat= ' || Substr(SQLERRM,1,100);
   -- SQLCODE i SQLERRM nie mogą wystąpić w instrukcji SQL!
  INSERT INTO Dziennik VALUES (komunikat);
  DBMS_OUTPUT.Put_line(‘Wystąpił inny błąd ');
END;
/
Jeśli blok, w którym wystąpił błąd, zawiera obsługę tego błędu, to po dokonaniu obsługi, sterowanie jest w zwykły sposób przekazywane do bloku go zawierającego (nadrzędnego). Jeśli nie zawiera, następuje propagacja  błędu do zawierających go bloków i albo tam nastąpi jego obsługa, albo błąd przechodzi do środowiska zewnętrznego.

Błąd, który wystąpił w sekcji wykonawczej bloku (między BEGIN i END) jest obsługiwany w sekcji EXCEPTION tego samego bloku. Błędy, które wystąpią w sekcji deklaracji lub w sekcji wyjątków, są od razu propagowane do bloku zawierającego dany blok.

Dobra praktyka programistyczna wymaga, aby każdy błąd został obsłużony – ewentualnie w klauzuli WHEN OTHERS THEN najbardziej zewnętrznego bloku. Aby móc stwierdzić, która instrukcja SQL spowodowała błąd, można używać podbloków z własną obsługą błędów, albo użyć licznika, zwiększającego się o jeden po wykonaniu każdej instrukcji SQL.

Wyjątki definiowane przez programistę

Wyjątki można deklarować samemu (w sekcji DECLARE) używając słowa kluczowego EXCEPTION
 
nazwa_wyjątku EXCEPTION;
 
powodować ich podniesienie (w sekcji instrukcji)
 
RAISE nazwa_wyjątku;
 
a następnie je obsługiwać (w sekcji EXCEPTION)
 
WHEN nazwa_wyjątku THEN …
 
W poniższym przykładzie po wykryciu, że w magazynie nie ma samochodu marki 'Fiat', następuje podniesienie wyjątku Brak_w_magazynie, a następnie jego obsługa, przez złożenie zamówienia i podniesienie wyjątku wyjście, do obsłużenia w bloku zawierającym dany blok – na przykład poprzez wydrukowanie i wysłanie zamówienia na samochody marki 'Fiat' do fabryki.

DECLARE
  brak_w_magazynie EXCEPTION;
  stan_magazynu NUMBER(5);
BEGIN
  SELECT m.Liczba
  INTO stan_magazynu
  FROM Magazyn m WHERE m.Marka = 'Fiat';
  IF stan_magazynu < 1 THEN
    RAISE brak_w_magazynie;
  END IF;
EXCEPTION
WHEN brak_w_magazynie THEN
  INSERT INTO Zamówienia VALUES ('Fiaty');
  RAISE wyjście;
END;
/
Jest też możliwość bezpośredniego podniesienia wyjątku za pomocą procedury raise_application_error przypisując mu numer i tekst. Wyjątek taki może zostać obsłużony albo w tym samym bloku, albo w aplikacji zewnętrznej, w której to wywołanie zostanie wykonane.
BEGIN
.....
  Raise_application_error(-20100,'Błąd');
EXCEPTION
WHEN OTHERS THEN
  IF SQLCODE = -20100 THEN
     DBMS_OUTPUT.Put_line('Błąd przechwycony!');
  END IF;
END;
/
Zakres numerów od –20000 do –20999 jako pierwszy argument procedury Raise_application_error jest zarezerwowany dla błędów definiowanych przez programistę.
 

Obsługa błędów przechwytywanych przez serwer bazy danych

Rozważmy obsługę błędu naruszenia więzów klucza obcego o numerze -2292.

DECLARE
  bl_klucz_o EXCEPTION;
  PRAGMA EXCEPTION_INIT (bl_klucz_o, -2292);

  v_deptno Dept.Deptno%TYPE := &p_deptno;
BEGIN
  DELETE FROM Dept 
  WHERE Deptno = v_deptno;
  COMMIT;
EXCEPTION
WHEN bl_klucz_o THEN
  DBMS_OUTPUT.Put_line('Nie można usunąć działu ' || TO_CHAR(v_deptno) || ', w którym są pracownicy.');
END;
/
PRAGMA oznacza dyrektywę wykonywaną przez kompilator. W tym przypadku dyrektywa o nazwie EXCEPTION_INIT powoduje przypisanie błędowi o numerze –2292 nazwy wyjątku bl_klucz_o. Do nazwy tej można następnie odwołać się w sekcji obsługi wyjątków w taki sam sposób, jak do każdego innego nazwanego wyjątku.

 

5.4 Instrukcje sterujące

Język PL/SQL posiada większość typowych instrukcji sterujących występujących w typowych językach programowania:
 
IF warunek THEN ciąg_instrukcji END IF;

IF warunek THEN ciąg_instrukcji
ELSE ciąg_instrukcji END IF;

 
Ponieważ wartością warunku może być True, False lub Null, więc istotne jest zrozumienie, że instrukcje po THEN są wykonywane wtedy, gdy wartością warunku jest True; instrukcje po ELSE są wykonywane wtedy, gdy wartością warunku jest False lub Null.

Jest jeszcze jedna postać instrukcji IF.
 
IF warunek THEN ciąg_instrukcji
ELSIF warunek THEN ciąg_instrukcji END IF;
 
Oto możliwe postacie iteracji:
 
LOOP
   ciąg instrukcji (w tym EXIT lub EXIT WHEN warunek)
END LOOP;

FOR zmienna IN wartość1 .. wartość2 LOOP
   ciąg instrukcji 
END LOOP;


WHILE warunek LOOP
   ciąg instrukcji 
 END LOOP;

 
Czasami jest konieczne użycie instrukcji pustej – w języku PL/SQL jest to Null – na przykład, w sytuacji gdy obsługa wyjątku jest pusta.
 

5.5 Kursory: dostęp do obszarów roboczych instrukcji SELECT

Używając dotychczas wprowadzonych konstrukcji języka PL/SQL nie było możliwe przeglądanie kolejno wszystkich wierszy będących wynikiem zapytania. Do tego celu jest potrzebny obiekt PL/SQL o nazwie kursor, który stanowi bufor, do którego są zapisywane, kolejno sprowadzane z bazy danych, wiersze wynikowe zapytania.

W sekcji deklaracji definiujemy kursor przyporządkowując mu instrukcję SELECT:
 
CURSOR nazwa_kursora
IS instrukcja_SELECT; -- (bez INTO!)

Następnie otwieramy go za pomocą instrukcji OPEN:
 
OPEN nazwa_kursora;

co oznacza wykonanie instrukcji SELECT przyporządkowanej kursorowi. Po czym możemy w pętli pobierać, przy użyciu instrukcji
 
FETCH nazwa_kursora INTO zmienna, …;
 
kolejny wiersz wyników zapytania i przypisywać go na zmienne PL/SQL w celu przetworzenia zawartych w nim informacji. Standardowo, instrukcja FETCH jest umieszczana w pętli i towarzyszy jej instrukcja
 
EXIT WHEN nazwa_kursora%NOTFOUND;

powodująca wyjście z pętli po sprowadzeniu wszystkich wierszy wynikowych. Na koniec, należy zamknąć kursor za pomocą instrukcji
 
CLOSE nazwa_kursora;
 
aby zwolnić zasoby systemu przyporządkowane kursorowi.

Przykład

DECLARE
  zarobki REAL:=0;
  CURSOR kursor_osoba IS
     SELECT * FROM Emp;
  rek_osoby kursor_osoba%ROWTYPE;
BEGIN
  OPEN kursor_osoba;
  LOOP
    FETCH kursor_osoba INTO rek_osoby;
    EXIT WHEN kursor_osoba%NOTFOUND;
    zarobki := zarobki + NVL(rek_osoby.Sal,0);
  END LOOP;
  DBMS_OUTPUT.Put_line('W sumie zarobki = '||zarobki);
  CLOSE kursor_osoba;
END;
/
W powyższym przykładzie najpierw deklarujemy kursor przyporządkowując mu instrukcję SELECT. Następnie otwieramy kursor, po czym w pętli wczytując na zmienną wierszową Rek_osoby, rozpatrujemy kolejne wiersze będące wynikiem zapytania, związanego z kursorem. Zwróćmy uwagę na użycie funkcji NVL gwarantującej poprawne sumowanie zarobków w przypadku, gdy zarobki niektórych pracowników nie zostały określone (nieokreślone zarobki interpretujemy jako równe 0) – w takim przypadku bez zastosowania funkcji NVL otrzymalibyśmy wynik Null (reprezentowane pustym miejscem przy wyświetlaniu).

Na liście instrukcji SELECT mogą się znajdować dowolne wyrażenia, przy czym jeśli nie jest to nazwa kolumny, musi być użyty alias kolumny, np.

CURSOR kursor IS
SELECT Ename, Sal+NVL(Comm,0) AS Sumaryczne
FROM Emp;
Dostępna też jest iteracja (instrukcje OPEN, FETCH i CLOSE są tu niejawne):
 
FOR rek_osoby IN kursor_osoba LOOP ... END LOOP;
 
W instrukcji SELECT zawartej w kursorze mogą występować zmienne PL/SQL. Ten sam kursor może być otwarty wielokrotnie – z różnymi wartościami zmiennych. Jest też dostępna wersja kursora z parametrami:
 
CURSOR nazwa_kursora(parametr typ_danych, ....) IS instrukcja-SELECT
 
Oto przykład użycia kursora z parametrami.
DECLARE
  CURSOR emp_cursor(v_deptno NUMBER, v_job VARCHAR2) IS
     SELECT e.Empno, e.Ename
     FROM Emp e
     WHERE e.Deptno = v_deptno AND e.Job = v_job;
BEGIN
  OPEN emp_cursor(10, 'CLERK');

Zmienne stanu kursora

Analogicznie do standardowych zmiennych opisujących stan obliczeń ostatnio wykonanej instrukcji SQL, tak samo każdemu kursorowi są przyporządkowane zmienne, opisujące stan wykonania instrukcji SELECT z nim związanej. Oto te zmienne:

Oto przykład użycia atrybutu %ISOPEN.

IF NOT prac_kursor%ISOPEN THEN
  OPEN prac_kursor;
END IF;
LOOP
   FETCH prac_kursor...

Aktualizacja wierszy za pomocą kursora

Przy wykonywaniu instrukcji SELECT można zakładać blokady na wiersze w celu ich modyfikacji. Klauzula
 
FOR UPDATE OF kolumna, kolumna, … 
 
powoduje założenie blokady w celu modyfikacji wartości w wierszach; natomiast stowarzyszona z nią w instrukcji UPDATE lub DELETE klauzula
 
WHERE CURRENT OF kursor
 
umożliwia modyfikację lub usunięcie sprowadzonego przez kursor wiersza odpowiedniej tabeli.

Przykład

Zastosujmy blokowanie wierszy poprzez kursor do zaprogramowania podwyższenia zarobków o 10% najmniej zarabiającym pracownikom oraz zmniejszenia zarobków o 10% najwięcej zarabiającym pracownikom.

DECLARE
  CURSOR kursor_osoba IS
    SELECT e.Ename, e.Sal FROM Emp e
    FOR UPDATE OF e.Sal;
  rek_osoby kursor_osoba%ROWTYPE;
BEGIN
  OPEN kursor_osoba;
  LOOP
    FETCH kursor_osoba INTO rek_osoby;
    EXIT WHEN kursor_osoba%NOTFOUND;
    IF rek_osoby.Sal < 10000 THEN
      UPDATE Emp SET Sal = Sal * 1.1
      WHERE CURRENT OF kursor_osoba;
    ELSIF rek_osoby.Sal > 100000 THEN
      UPDATE Emp SET Sal = Sal * 0.9
      WHERE CURRENT OF kursor_osoba;
    END IF;
    /* zamiast modyfikować, możemy też usunąć wiersz, np.
       DELETE Emp WHERE CURRENT OF kursor_osoba; */
  END LOOP;
  CLOSE kursor_osoba;
  COMMIT;
END;
/

Typy kursorowe i użycie zmiennych kursorowych

Są one potrzebne, aby usunąć ograniczenia związane z niemożliwością przekazywania kursora jako parametru procedury/funkcji.

DECLARE
  TYPE Typ_produktów IS
  REF CURSOR RETURN Produkty%ROWTYPE; /* silny typ */
  kursor1 Typ_produktów;
  TYPE Typ_kursorowy IS REF CURSOR; /* słaby typ */
  kursor2 Typ_kursorowy;
Można przekazywać jako parametr zmienną kursorową wraz z jej stanem. Wartość zmiennej kursorowej jest traktowana jak wskaźnik. Instrukcję SELECT wiąże się ze zmienną kursorową w instrukcji OPEN. Poza tym, użycie zmiennych kursorowych jest podobne do użycia kursorów (nie można ich tylko używać w pętli kursorowej FOR):
DECLARE
  TYPE Typ_kursorowy IS REF CURSOR;
  z_kurs_klienci Typ_kursorowy;
BEGIN
  OPEN z_kurs_klienci FOR
    SELECT k.Nazwisko, k.Imię, k.Telefon FROM Klienci k;
  WHILE z_kurs_klienci%FOUND LOOP ... END LOOP;
  CLOSE z_kurs_klienci;
END;
/


 


Podsumowanie

Przedstawiliśmy podstawowe elementy kodu PL/SQL wykonywanego przez serwer bazy danych Oracle. W następnym wykładzie zostanie pokazane jak używać tego kodu w jednostkach programistycznych zapisywanych w bazie danych razem z innymi obiektami.

 


Słownik pojęć

blok PL/SQL (blok anonimowy) – podstawowa jednostka kodu w PL/SQL złożona z trzech sekcji: deklaracji, instrukcji i obsługi wyjątków.

SELECT INTO - instrukcja SELECT, w której dodatkowo występuje klauzula INTO wskazująca, na jakich zmiennych ma być zapisany wynik instrukcji SELECT (musi się on składać z dokładnie jednego wiersza).

obsługa wyjątków - jest realizowana w sekcji wyjątków bloku PL/SQL.

EXCEPTION - słowo kluczowe rozpoczynające sekcję wyjątków w PL/SQL.

kursor – struktura danych umożliwiająca sprowadzanie z bazy danych kolejnych wierszy wyniku zapytania.

FOR UPDATE OF - klauzula instrukcji SELECT w definicji kursora umożliwiająca aktualizację wierszy w bazie danych poprzez otworzony kursor.

 


Zadania

Zadanie 1

Napisz blok PL/SQL służący do wprowadzania nowego pracownika (dane mają być wprowadzone na bieżąco). Najpierw sprawdź czy dane są poprawne w tym korzystając także z obsługi wyjątków. Po wykonaniu instrukcji INSERT spytaj użytkownika, czy chce zatwierdzić zmiany. Jeśli odpowiedź będzie NIE, wycofaj zmianę; jeśli odpowiedź będzie TAK, zatwierdź zmianę i wypisz wprowadzone wartości na ekran.

Zadanie 2

Utwórz tabelę Emps_in_Depts (Deptno NUMBER(2), Pracownicy VARCHAR2(4000)).
Napisz blok PL/SQL, w którym do tabeli Zestawienia są wprowadzone wiersze postaci (deptno,ename1$ename2$....enamek$), gdzie ename1,ename2,...,enamek są wszystkimi pracownikami zatrudnionymi w dziale o numerze deptno np. (10,CLARK$KING$MILLER$)
 



Strona przygotowana przez Lecha Banachowskiego. Ostatnia aktualizacja - 01/24/05 .