Dotychczas omawiane przykłady były fragmentami kodu uruchamianymi przez użytkownika słowem GO
. Nie były one jednak zapisywane trwale w bazie danych. Można zachować na stałe kod napisany w Transact-SQL pod postacią tzw. procedury składowanej (stored procedure). Procedurę taką będą mogli w przyszłości uruchamiać użytkownicy, o ile oczywiście posiadają do niej uprawnienia.
Procedury składowane są jednym z podstawowych i najczęściej stosowanych mechanizmów przy oprogramowaniu strony serwera aplikacji bazodanowej. Typową sytuacją w aplikacjach jest taka, w której większość lub czasem wszystkie operacje na danych są wykonywane poprzez procedury składowane.
Stosowanie procedur składowanych ma bardzo istotne zalety:
SELECT
, INSERT
, UPDATE
, DELETE
bezpośrednio do tabel, lecz nadajemy uprawnienia wyłącznie do wykonania odpowiednich procedur.Aby utworzyć procedurę piszemy:
CREATE PROCEDURE nazwa_procedury lista parametrów AS BEGIN instrukcje Transact-SQL END
Kod istniejącej procedury możemy podmienić w podobny sposób, zamieniając słowo CREATE
na ALTER
. Procedury usuwamy poleceniem DROP PROCEDURE
.
Listę istniejących procedur możemy zaobserwować w narzędziu Management Studio rozwijając gałąź Programability, a następnie Stored procedures. Z tego poziomu możemy rozpocząć modyfikację istniejącej procedury lub ją usunąć. Istnieje tutaj też możliwość utworzenia nowej procedury, lecz graficzny interfejs niewiele nam w tym pomoże. Otworzy się jedynie nowa zakładka z nagłówkiem procedury.
Przykład:
Procedura wprowadzająca nowego pracownika z numerem empno większym o jeden od maksymalnego numeru istniejącego w tabeli:
CREATE PROCEDURE wstaw_emp @ename VARCHAR(10), @sal DECIMAL(8,2) AS BEGIN DECLARE @empno INT SELECT @empno = ISNULL(MAX(empno), 0) + 1 FROM emp INSERT INTO emp (empno, ename, sal) VALUES (@empno, @ename, @sal) END
Aby wywołać powyższą procedurę, napiszemy na przykład:
EXECUTE wstaw_emp 'Kowalski', 1000
Lub w skrócie:
EXEC wstaw_emp 'Kowalski', 1000
Istnieje też składnia rozszerzona, pozwalająca podać wartości parametrów razem z ich nazwami:
EXECUTE wstaw_emp @ename = 'Kowalski', @sal = 1000
Kolejny przykład będzie nieco bardziej skomplikowany. Procedura będzie służyła do wstawiania działów do tabeli „dept”. Przed wstawieniem sprawdzimy, czy dział o podanej nazwie lub lokalizacji już istnieje. Jeśli tak by się stało, nie dodamy nowego rekordu:
CREATE PROCEDURE wstaw_dzial @deptno INT, @dname VARCHAR(14), @loc VARCHAR(13) AS BEGIN DECLARE @ile INT SELECT @ile = COUNT(*) FROM dept WHERE dname = @dname OR loc = @loc IF @ile = 0 INSERT INTO dept (deptno, dname, loc) VALUES (@deptno, @dname, @loc) ELSE PRINT 'Taki dzial juz istnieje' END
Sprawdzenie czy rekord istnieje zostało zrealizowane poprzez deklarację zmiennej i przypisanie na nią wartości funkcji COUNT
. To samo zadanie możemy rozwiązać nieco prościej:
CREATE PROCEDURE wstaw_dzial @deptno INT, @dname VARCHAR(14), @loc VARCHAR(13) AS BEGIN IF NOT EXISTS (SELECT 'X' FROM dept WHERE dname = @dname OR loc = @loc) INSERT INTO dept (deptno, dname, loc) VALUES (@deptno, @dname, @loc) ELSE PRINT 'Taki dzial juz istnieje' END
Jak widzieliśmy w powyższych przykładach, procedury zwykle posiadają parametry, dzięki którym osoba je wywołująca może przekazać wartości do wnętrza procedury. Nazwa parametru, podobnie jak zmiennej, musi zaczynać się od znaku @. Musi mieć też określony typ danych. Parametr może mieć przypisaną wartość domyślną czyli taką, która zostanie przyjęta, gdy w trakcie wywołania nie zostanie podana wartość. Możemy też zadeklarować parametr jako wyjściowy.
@nazwa_parametru TYP [=wartość_domyślna] [OUTPUT]
Poprzez parametr wyjściowy procedura może zwrócić wartość użytkownikowi.
W poniższym przykładzie procedura dla podanego empno modyfikuje zarobki o zadany procent (domyślnie 20) oraz zwraca przez parametr wyjściowy nową wartość.
CREATE PROCEDURE pensja @Empno INT, @Nowe_zarobki DECIMAL(6,2) OUTPUT, @Procent INT = 20 AS BEGIN SELECT @Nowe_zarobki = Sal + Sal * @Procent / 100 FROM Emp WHERE Empno = @Empno UPDATE Emp SET Sal = @Nowe_zarobki WHERE Empno = @Empno END
Wywołanie procedury zwracającej dane przez parametr wyjściowy odbywa się w sposób analogiczny do zwykłej procedury. Jeśli jednak chcielibyśmy odebrać nową wartość zarobków, piszemy:
DECLARE @New_sal DECIMAL(6,2) EXECUTE Pensja 1234, @New_sal OUTPUT PRINT @New_sal
Ten fragment kodu piszemy jedynie, gdy chcemy przetestować procedurę z OUTPUT
lub chcemy, aby zwrócona wartość była użyta w dalszych obliczeniach. Pamiętajmy, że w praktyce procedury są uruchamiane zwykle przez aplikacje je wykorzystujące, a więc sterownik bazy danych narzędzia, w którym napisana była aplikacja, będzie obsługiwał pobranie wartości z parametru wyjściowego.
Innym sposobem, w jaki procedura może zwracać wartość, jest instrukcja RETURN
. Powoduje ona natychmiastowe wyjście z procedury. Może przekazywać wyłącznie pojedynczą wartość typu INT
w przeciwieństwie do parametrów typu OUTPUT
, których może być wiele i mogą być różnych typów.
CREATE PROCEDURE Ile_pracownikow AS BEGIN DECLARE @ile INT SELECT @ile = COUNT(*) FROM Emp RETURN @ile END
Aby wywołać procedurę i pobrać wartość piszemy:
DECLARE @Ilu_prac Int EXECUTE @Ilu_prac = Ile_pracownikow PRINT @Ilu_prac
Istnieje jeszcze jeden sposób, w jaki procedura może zwracać dane na zewnątrz. Jest nim tzw. resultset, czyli zestaw rekordów będących wynikiem działania instrukcji SELECT
. Należy pamiętać, że przekazywany jest wynik ostatniej instrukcji SELECT
w procedurze.
Poniższa procedura zwróci w postaci tabeli wszystkich pracowników z zadanego parametrem działu.
CREATE PROCEDURE pracownicy_dzialu @dzial INT AS BEGIN SELECT * FROM emp WHERE deptno = @dzial END
Aby wywołać procedurę, piszemy:
EXECUTE pracownicy_dzialu 20
Resultset jest jak widzimy, bardzo łatwą w użyciu i elastyczną metodą zwracania danych przez procedury składowane. Z tego względu jest metodą najczęściej wykorzystywaną.
Z punktów 3, 4 i 5 dowiedzieliśmy się, że istnieją trzy sposoby, w jakie procedura może zwracać wartości. Wybór odpowiedniej metody będzie zależał od tego, jakie dane mają być zwracane oraz tego, które metody są dostępne w sterowniku bazy danych języka wykorzystywanego do tworzenia aplikacji, która ma z procedury korzystać.
OUTPUT
użyjemy wtedy, gdy procedura ma zwrócić kilka pojedynczych wartości określonych typów.RETURN
użyjemy, gdy procedura ma zwrócić jedną wartość typu INT
.Poniżej przedstawione zostało rozwiązanie tego samego zadania w trzech wersjach. Każda z procedur zwraca liczbę rekordów w tabeli „emp”.
Wersja z OUTPUT:
CREATE PROCEDURE IleOutput @Ile INT OUTPUT AS BEGIN SELECT @Ile = COUNT(*) FROM emp END
Wywołanie:
DECLARE @a INT EXEC IleOutput @a OUTPUT PRINT @a
Wersja z RETURN:
CREATE PROCEDURE IleReturn AS BEGIN DECLARE @Ile INT SELECT @Ile = COUNT(*) FROM emp RETURN @Ile END
Wywołanie:
DECLARE @a INT EXEC @a = IleReturn PRINT @a
Wersja zwracająca przez resultset:
CREATE PROCEDURE IleResultset AS BEGIN SELECT COUNT(*) FROM emp END
Wywołanie:
EXEC IleResultset
Tworzenie procedury składowanej w języku PL/SQL wygląda następująco:
CREATE [OR REPLACE] PROCEDURE nazwa (par [IN/OUT/INOUT] TYP, ...) AS deklaracje BEGIN kod procedury END;
Podstawowe różnice w stosunku do Transact-SQL:
CREATE
lecz możemy napisać również CREATE OR REPLACE
dzięki czemu procedura zostanie utworzona, lub jeśli już istnieje, zamieniona.IN
, OUT
lub INOUT
określając, czy jest to parametr wejściowy, wyjściowy lub dwukierunkowy. Jeśli nie określimy, domyślnie będzie to parametr wejściowy.VARCHAR
, a nie VARCHAR(10)
.Oto rozwiązanie przykładu z punktu 2, dotyczącego procedury wstawiającej nowy dział:
CREATE OR REPLACE PROCEDURE wstaw (nr NUMBER, nazwa VARCHAR2, lokalizacja VARCHAR2) AS ile INTEGER; BEGIN SELECT COUNT(*) INTO ile FROM dept WHERE dname = nazwa or loc = lokalizacja; IF ile = 1 THEN raise_application_error(-20500, 'Dzial juz istnieje'); ELSE INSERT INTO dept (deptno, dname, loc) VALUES (nr, nazwa, lokalizacja); END IF; END;