Wykład 11

Transact-SQL – instrukcje sterujące

1. Wstęp

W dotychczasowych przykładach wykonanie programu przebiegało zawsze po kolei, od pierwszej do ostatniej linii. W przykładach tego wykładu sytuacja nie będzie już taka prosta. Instrukcje sterujące są bardzo ważnym elementem każdego języka programowania. Pozwalają wykonać różne fragmenty kodu w zależności od zajścia określonych warunków. Umożliwiają też tworzenie pętli, a więc wielokrotne wykonanie pewnego fragmentu kodu.

2. Instrukcja warunkowa IF

IF jest podstawową instrukcją sterującą w większości języków programowania. Umożliwia rozdzielenie wykonania programu w zależności od tego, czy spełnione są określone warunki. Składnia instrukcji IF wygląda następująco:

IF warunek
   ciąg instrukcji 1
ELSE
   ciąg instrukcji 2

Gdy warunek określony w IF będzie spełniony, wykona się pierwszy ciąg instrukcji. W przeciwnym przypadku drugi. Instrukcja IF nie musi zawierać ELSE. W takim przypadku ciąg instrukcji wykona się, gdy spełniony jest warunek, a w przeciwnym przypadku ciąg instrukcji zostanie pominięty i wykonanie programu od razu przejdzie dalej.

Warunek logiczny umieszczony po słowie IF może używać tych samych operatorów oraz funkcji, których używaliśmy, gdy omawialiśmy warunek WHERE instrukcji SELECT. Możemy więc łączyć kilka warunków spójnikami logicznymi (AND, OR, NOT), używać nawiasów, czy innych operatorów (np. LIKE, BETWEEN, IN). Podobnie jak w przypadku klauzuli WHERE, warunek jest spełniony wyłącznie wtedy, gdy zwraca logiczną wartość „prawda”. Pierwszy ciąg instrukcji wykona się więc wyłącznie w takim przypadku. Gdy wynikiem jest „fałsz” lub NULL, wykona się blok znajdujący się po słowie ELSE.

Ciąg instrukcji powinien znajdować się między słowami BEGIN i END. Słowa te można pominąć, gdy warunkiem ma być objęta pojedyncza instrukcja. Przykład:

DECLARE @Ile INT
SELECT @Ile = COUNT(*) FROM emp
IF @Ile < 16 BEGIN
   INSERT INTO emp (empno, ename, sal) VALUES (1234, 'Kowalski', 1000)
   PRINT 'Wstawiono Kowalskiego'
END ELSE
   PRINT 'Nie wstawiono danych'

Jak widać na powyższym przykładzie, jeśli pensja jest większa niż 0 wykonają się dwie instrukcje (INSERT oraz PRINT). Konieczne jest więc użycie słów BEGIN i END. Nie trzeba jednak ich stosować po ELSE, gdyż znajduje się tam tylko jedna instrukcja.

Używając instrukcji sterujących warto robić wcięcia korzystając z przycisku tabulacji. Dzięki temu łatwiej będzie zorientować się, który fragment kodu wykona się przy zajściu określonego warunku. Pamiętajmy, że w bardziej zaawansowanych programach instrukcji sterujących może pojawić się bardzo wiele, mogą być zagnieżdżone jedna w drugiej itd.

W języku Transact-SQL istnieje możliwość umieszczania podzapytań wewnątrz warunku logicznego. Niejednokrotnie pozwala to znacznie uprościć kod. Oto kod ostatniego przykładu z podzapytaniem w instrukcji IF. Jak widać, nie ma w tym przypadku konieczności deklarowania zmiennej:

IF (SELECT COUNT(*) FROM emp) < 16 BEGIN
   INSERT INTO emp (empno, ename, sal) VALUES (1234, 'Kowalski', 1000)
   PRINT 'Wstawiono Kowalskiego'
END ELSE
   PRINT 'Nie wstawiono danych'

Może zajść konieczność rozdzielenia sterowania programu na więcej niż dwie ścieżki. Niektóre języki programowania oferują konstrukcje typu ELSEIF. Konstrukcja taka nie jest bezpośrednio dostępna w Transact-SQL. Możemy ją jednak zastąpić kilkoma konstrukcjami IF ... ELSE. Przykład:

DECLARE @sal INT
SELECT @sal = sal FROM emp WHERE empno = 1234
IF @sal < 1000 BEGIN
   UPDATE emp SET sal = sal + 100 WHERE empno = 1234
   PRINT 'Zwiększono pensję'
END ELSE IF @sal > 2000 BEGIN
   UPDATE emp SET sal = sal - 100 WHERE empno = 1234
   PRINT 'Zmniejszono pensję'
END ELSE
   PRINT 'Pensja niezmieniona'

3. Pętla WHILE

Pętla jest konstrukcją programistyczną pozwalającą na wielokrotne wykonanie fragmentu kodu. Języki programowania ogólnego przeznaczenia oferują zwykle kilka konstrukcji pętli. W Transact-SQL nie mamy wyboru. Istnieje wyłącznie pętla WHILE czyli taka, w której fragment kodu będzie wykonywany wielokrotnie do czasu, gdy przestanie być spełniony warunek logiczny. Nie jest to duża wada, gdyż przy pomocy pętli WHILE jesteśmy w stanie zrealizować każdy inny rodzaj pętli. Pętla WHILE ma następującą postać:

WHILE warunek
   ciąg instrukcji

Pisząc tego typu pętlę powinniśmy upewnić się, że warunek logiczny kiedyś przestanie być spełniony. W przeciwnym przypadku nasz program wejdzie w nieskończoną pętlę.

Przykład użycia pętli w celu wypisania liczb od 1 do 10:

DECLARE @licznik INT
SET @licznik = 1
WHILE @licznik <= 10 BEGIN
   PRINT @licznik
   SET @licznik = @licznik + 1
END

Wewnątrz pętli WHILE mamy możliwość umieszczenia instrukcji natychmiastowego przerwania pętli (BREAK) oraz przejścia do kolejnej iteracji bez wykonywania reszty kodu (CONTINUE). Przykład:

WHILE (SELECT AVG(Sal) FROM Emp) < 2000
BEGIN
   UPDATE Emp SET Sal = Sal * 1.1
   IF (SELECT MAX(Sal) FROM Emp) > 4000
      BREAK
   ELSE
      CONTINUE
END

Program ten będzie zwiększał pensję pracownikom o 10% do czasu, gdy średnia przekroczy 2000. Jeśli jednak wcześniej maksymalna pensja przekroczy 4000 program zakończy się natychmiast. Użycie instrukcji CONTINUE w tym przypadku nie jest konieczne, gdyż po niej i tak nie znajduje się żaden kod, a więc wykonałby się kolejny obrót pętli.

4. Wywoływanie i obsługa błędów

Wewnątrz kodu Transact-SQL może zaistnieć konieczność wywołania błędu, który będzie obsłużony przez aplikację kliencką. Błędy takie będą przez aplikację kliencką traktowane na równi z błędami zgłaszanymi przez system zarządzania bazą danych.

RAISERROR (komunikat|numer, severity, state)

Gdzie:

Wartości parametrów Severity i State są przekazywane do aplikacji klienta, dzięki czemu różne błędy mogą być obsługiwane w różny sposób.

Umieszczona w kodzie procedury instrukcja zwraca komunikat o błędzie (zadeklarowany w parametrze Message) oraz Severity i State. Dla wartości Severity większej od 10 następuje przerwanie realizacji kodu procedury i (ewentualnie) przekazanie sterowania do bloku CATCH.

Błędy, zarówno te zgłaszane przez programistę, jak też zgłaszane przez system, mogą być obsłużone, a więc w momencie wystąpienia błędu sterowanie zostanie przekazane do odpowiedniego bloku kodu. Obsługa błędów może odbywać się w deklarowanych bezpośrednio po sobie blokach:

BEGIN TRY
   Kod
END TRY
BEGIN CATCH
   Kod wykonywany, gdy wystąpi błąd
END CATCH

Zasadnicze instrukcje naszego programu umieszczane są w bloku TRY i wykonywane, dopóki nie zostanie podniesiony błąd. W przypadku wystąpienia błędu o wartości severity większej niż 10 sterowanie zostaje przekazane do bloku CATCH i wykonywane są dalej instrukcje zawarte w tym bloku.

Bloków TRY i CATCH może być w procedurze kilka , mogą być w sobie zagnieżdżane.

Przekazanie sterowania do bloku CATCH może nastąpić zarówno po podniesieniu błędu instrukcją RAISERROR, jak też po podniesieniu przez system bazy danych błędu o severity większym niż 10.

Poniższy przykład ilustruje zgłaszanie i obsługę błędów w Transact-SQL:

DECLARE @Level Int 
SET @Level = 1 -- tutaj podstaw wartość 1 lub 2
DECLARE @StrInfo Varchar(30) 
BEGIN TRY
IF @Level = 1
   BEGIN
      SET @StrInfo = 'Błąd niekrytyczny'
      RAISERROR (@StrInfo, 1, 10)
      PRINT 'Przeszliśmy błąd o numerze 10'
   END
IF @Level = 2
   BEGIN
      SET @StrInfo = 'Błąd krytyczny'
      RAISERROR (@StrInfo, 11, 20)
      PRINT 'Czy przeszliśmy błąd o numerze 20 ?'
   END
END TRY
BEGIN CATCH
DECLARE @ErrMsg NVARCHAR(100), @ErrSev INT, @ErrSt INT;
SELECT @ErrMsg = ERROR_MESSAGE(), @ErrSev = ERROR_SEVERITY(), @ErrSt = ERROR_STATE();
PRINT @ErrMsg + ' Severity = ' + Cast(@ErrSev As Varchar(3)) + ' State = ' + Cast(@ErrSt As Varchar(3))
END CATCH

5. Instrukcje sterujące w PL/SQL

Instrukcja warunkowa IF w PL/SQL ma następującą postać:

IF warunek THEN
   ciąg instrukcji
ELSIF warunek THEN
   ciąg instrukcji
ELSE
   ciąg instrukcji
END IF;

ELSE oraz ELSIF nie muszą wystąpić. ELEIF może pojawić się wielokrotnie.

Oracle oferuje trzy konstrukcje pętli:

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;

Przykład użycia instrukcji warunkowej w PL/SQL:

DECLARE
   pensja INTEGER;
BEGIN
   SELECT sal INTO pensja FROM emp WHERE empno = 1234;
   IF pensja < 1000 THEN
      UPDATE emp SET sal = sal + 100 WHERE empno = 1234;
      dbms_output.put_line ('Zwiększono pensję');
   ELSIF pensja > 2000 THEN
      UPDATE emp SET sal = sal - 100 WHERE empno = 1234;
      dbms_output.put_line 'Zmniejszono pensję';
   ELSE
      PRINT 'Pensja niezmieniona';
   END IF;
END;