Ćwiczenie 5 Tworzenie procedur przechowywanych oraz kursorów w MS SQL Server 1 1. Część teoretyczna. 1.1. Wstęp. Każdy szanujący się serwer baz danych zapewnia obsługę procedur przechowywanych (ang. Stored Procedure). Procedura składowana jest obiektem bazy BD, który zawiera zbiór określonych instrukcji języka SQL, a w zasadzie jego proceduralnej odmiany. Procedura służy do wykonywania często powtarzanych czynności. Zamiast pisać wielokrotnie ten sam, składający się z kilkudziesięciu linii kod SQL, możemy go umieścić w procedurze, którą następnie możemy bardzo łatwo wywoływać. Aby można było wpływać na działanie procedury z zewnątrz, można je sparametryzować. Dzięki temu możemy przekazać jej pewne wartości, które będą w niej wykorzystane. Zalety procedur składowanych to: 1.2. dzielenia kodu logiki biznesowej pomiędzy różne aplikacje, implementacja ochrony dostępu do tabel systemowych, zapewnienie mechanizmów bezpieczeństwa, zwiększenie wydajności, redukcja ruchu sieciowego Polecenia SQL. Do tworzenia procedur służy następujące polecenie SQL: CREATE PROC [ EDURE ] procedure_name [ ; number ] [ { @parameter data_type } [ VARYING ] [ = default ] [ OUTPUT ] ] [ ,...n ] [ WITH { RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION } ] [ FOR REPLICATION ] AS sql_statement [ ...n ] Następujące opcje oznaczają: procedure_name @parameter data_type VARYING OUTPUT RECOMPILE ENCRYPTION FOR REPLICATION nazwa procedury, może być poprzedzona znakiem # co oznacza lokalną procedurę tymczasową, lub znakiami ## co oznacza globalną procedurę tymczasową nazwa parametru poprzedzona małpą @ typ parametru oznacza iż procedura zwraca w wyniku kursor oznacza parametr wyjściowy powoduje iż SQL Server nie będzie przechowywał skompilowanej wersji procedury w Cachu, tylko kompilował ją przy każdym uruchomieniu treść (kod) zwykłej procedury znajduje się w polu text, w tabeli syscomments; każdy użytkownik może ją wydobyć i odczytać; czasem jednak chcemy aby nikt się nie dowiedział jak procedura jest implementowana – wtedy używamy tej opcji, co spowoduje iż kod będzie przechowywany w wersji zaszyfrowanej procedura nie może być uruchamiana na komputerze Subskrybenta; jest ona używana jako filtr, który umożliwia jej wykonywanie jedynie podczas replikacji; ta opcja nie może być używana z RECOMPILE. Analogiczną składnię posiada polecenie służące do zmiany procedur: ALTER PROC [ EDURE ] procedure_name [ ; number ] [ { @parameter data_type } [ VARYING ] [ = default ] [ OUTPUT ] ] [ ,...n ] [ WITH { RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION } ] [ FOR REPLICATION ] AS sql_statement [ ...n ] 2 Do usuwania procedur służy natomiast proste polecenie: DROP PROCEDURE { procedure } [ ,...n ] za pomocą którego możemy usunąć jedną lub kilka procedur na raz. 1.3. Procedura wybierająca. Oto prosta procedura wybierająca wiersze z tabeli (będziemy się opierać o bazę Northwind): CREATE PROCEDURE PokazProdukty AS SELECT * FROM products GO Do uruchamiania procedur służy polecenie „Execute”: exec PokazProdukty; 1.4. Procedura z parametrem wejściowym. Żeby działanie procedury było elastyczne musimy dodać do niej parametr: CREATE PROCEDURE PokazProdukt @id integer AS SELECT * FROM products WHERE ProductId = @id GO Aby uruchomić ww procedurę należy przekazać parametr – identyfikator produktu: exec PokazProdukt 12; 1.5. Procedura z parametrem wyjściowym. Teraz mamy 2 parametry : wejściowy i wyjściowy: CREATE PROCEDURE PokazPracownika @id integer, @pelna_nazwa varchar(40) OUTPUT AS SELECT @Pelna_nazwa = (TitleOfCourtesy+' '+FirstName+' '+LastName) FROM Employees WHERE EmployeeID = @id Aby uruchomić ww procedurę należy przekazać parametr wejściowy oraz pobrać parametr wyjściowy. Aby to zrobić musimy utworzyć najpierw zmienną lokalną typu tekstowego na przechowywanie parametru wyjściowego. Dalej wywołujemy procedurę, która wizualnie nie zwraca żadnego wyniku. Aby pokazać rezultat użyjemy funkcji PRINT: DECLARE @nazwa varchar(80); exec PokazPracownika 5, @nazwa output; PRINT 'NAZWA PRACOWNIKA : '+@nazwa; 1.6. Deklaracja zmiennych lokalnych. Czasem musimy gdzieś przechować jakąś wartość. Do tego celu służą zmienne, których już użyliśmy powyżej. Do deklaracji zmiennych służy instrukcja: DECLARE {{ @local_variable data_type } Nazwa zmiennej musi być poprzedzona małpką @. Przykład: DECLARE @numer integer; Zmienne lokalne muszą się być wykorzystane w tym samym pakiecie. To oznaczy, że dla linii kodu rozdzielonych instrukcją GO zmienne lokalne będą niewidoczne. Zmienne globalne są poprzedzone podwójną małpką @@. 3 1.7. Przypisywanie wartości zmiennych lokalnych. Do przypisania wartości zmiennej służy instrukcja: SET { @local_variable = expression } Przykład użycia: SET @tekst = ‘Hello World’; 1.8. Zwracanie wartości jako rekord. Niekiedy chcemy zwrócić z procedury jakąś pojedynczą wartość lub zbiór wartości, ale bez użycia parametrów, a jako rekord. Można to zrealizować za pomocą polecenia SELECT bez frazy FROM: SELECT ‘Hello world!’; Gdy zwracamy kilka wartości, oddzielamy je przecinkami: SELECT 1, 2, 3; 1.9. Instrukcja warunkowa IF ... ELSE. Wewnątrz procedury możemy korzystać z różnych instrukcji kontrolujących bieg programu. Jedną z powszechniejszych jest instrukcja warunkowa IF. Jej składnia jest bardzo prosta: IF Boolean_expression { sql_statement | statement_block } [ ELSE { sql_statement | statement_block } ] Przykład wykorzystania: IF (@zmienna is null) THEN SET @zmienna = 0 ELSE SET @zmienna = @zmienna + 1; 1.10. Bloki instrukcji. Niekiedy zachodzi potrzeba umieszczenia wielu instrukcji, tam gdzie wymagana jest pojedyncza instrukcja. Często ma to miejsce w instrukcji warunkowej. Do tego celu używa się instrukcji złożonej: BEGIN { sql_statement | statement_block } END Przykład użycia: IF (@zmienna is null) THEN BEGIN SET @zmienna = 0; SET @ok = 1; END 1.11. Generowanie błędów wewnątrz procedury. Czasem zaistnieje taka sytuacja iż wewnątrz procedury chcemy zgłosić błąd. Zazwyczaj ma to miejsce podczas walidacji parametrów – kiedy mają one nieprawidłowe wartości. Do tego celu służy instrukcja RAISEERROR: RAISERROR ( { msg_id | msg_str } { , severity , state } [ , argument [ ,...n ] ] ) [ WITH option [ ,...n ] ] Najczęściej używamy jej z 3 parametrami (pełny opis w dokumentacji Transact-SQL): msg_str severity treść komunikatu błędu określa na ile dany błąd jest krytyczny, im wyższa tym bardziej poważny 4 state błąd; wartości od 0 do 18 mogą być wywoływane przez dowolnego użytkownika, poziom od 18 do 25 jest zarezerwowany dla członków roli sysadmin liczba całkowita z przedziału od 1 do 127 określająca stan wywołania błądu Przykład wykorzystania: IF @kwota < 0 BEGIN RAISERROR('Kwota musi być dodatnia!', 14, 1) RETURN END 1.12. Pętla WHILE. Język Transact-SQL posiada nawet tak ciekawe mechanizmy jak pętle. Ich składnia jest następująca: WHILE Boolean_expression { sql_statement | statement_block } [ BREAK ] { sql_statement | statement_block } [ CONTINUE ] Jak widać w bloku pętli mogą wystąpić instrukcje BREAK oraz CONTINUE, których działanie jest oczywiste. Przykład wykorzystania: WHILE @licznik > 0 exec procedura; 1.13. Uruchamianie SQL wygenerowanego w locie. Nieraz nie możemy określić kodu SQL na etapie jego tworzenia. Jesteśmy zmuszeni najpierw go wygenerować dynamicznie w procedurze i potem uruchomić. Do tego celu służy znana nam już instrukcja EXEC: EXEC [ UTE ] ( { @string_variable | [ N ] 'tsql_string' } [ + ...n ] ) Przykład użycia: EXEC(‘select * from customers’); 1.14. Kursory. Kursor to jest kolekcja elementów danych w buforze, która odwzorowuje stan aktualny tabel bazy danych. W tej kolekcji w każdy moment może być aktywnym tylko jeden rekord. Kursor zawiera mechanizmy przesuwania pomiędzy rekordami oraz wymiany danymi pomiędzy bazą a kursorem. Kursorów używamy podobnie jak zwykłego polecenia wybierającego SELECT, z tą różnicą że polecenie SELECT przerabia i zwraca cały wynik w jednej instrukcji, a kursor umożliwia nam iterację rekord po rekordzie. Deklarowanie kursorów jest opisane w HELPu. Użycie kursora w języku Transact-SQL jest następujące (przykład): /* deklaracja zmiennych do przechowywania imienia i nazwiska pracownika */ DECLARE @lname varchar(40), @fname varchar(20) /* deklaracja kursora */ DECLARE employees_cursor CURSOR FOR /* polecenie SELECT przypisane do kursora */ SELECT lastname, firstname FROM employees ORDER BY 1,2 /* otwarcie kursora */ OPEN employees_cursor /* Pobranie rekordu z kursora, do odpowiednich zmiennych. Kolejność zmiennych odpowiada kolejności pól w poleceniu SELECT. Zawsze pierwszy rekord należy pobrać przed pętlą WHILE */ FETCH NEXT FROM employees_cursor INTO @lname, @fname 5 /* Pętla WHILE sprawdza zmienną globalną @@FETCH_STATUS, która określa czy jest następny rekord do pobrania */ WHILE @@FETCH_STATUS = 0 BEGIN /* wypisanie pracownika na ekranie */ PRINT 'Pracownik: ' + @fname + ' ' + @lname; /* pobranie kolejnego rekordu */ FETCH NEXT FROM employees_cursor INTO @lname, @fname END /* zamknięcie kursora */ CLOSE employees_cursor /* zwolnienie zasobów */ DEALLOCATE employees_cursor GO Szczegóły są opisane w komentarzach. Kursory mogą być także zwracane jako rezultat procedury, przykłady są opisane w HELPu (patrz hasło „CREATE PROCEDURE”). 1.15. Funkcje wbudowane w serwer. SQL Server zapewnia nam bardzo bogate zaplecze jeśli chodzi o różnego rodzaju funkcje, które możemy wykorzystać w naszych procedurach. Ich lista jest bardzo długa, i nie sposób wymienić ich wszystkich. Najczęściej używane kategorie funkcji to tekstowe, matematyczne, daty i czasu, statystyczne czy systemowe. Poniżej znajduje się lista często używanych funkcji, których nazwy dość dobrze opisują ich działanie: SUBSTRING, REPLACE, UPPER, LOWER, LTRIM, RTRIM ABS, FLOOR, ROUND, SQRT, SIGN, RAND, POWER, EXP, SIN, COS GETDATE, YEAR, MONTH, DAY NULLIF, CURRENT_USER, CASE HAS_DBACCESS, IS_MEMBER, IS_SRVROLEMEMBER Po dokładny opis odsyłam do help-a Transact-SQL, hasło functions. 2. Zadania do wykonania (w oparciu o bazę Pubs). 2.1.Utwórz procedurę „Title_authors”, zwracającą tytuły książek i nazwiśka ich autorów. Dla formowania zapytań SQL do bazy danych wykorzystaj tabeli: Titles, Authors oraz Titleauthor. Za dopomogą procedury systemowej sp_helptext sprawdź kod źródłowy stworzonej procedury. Napisz kod dla uruchomienia procedury. 2.2.Utwórz procedurę „MyProc” wybierającą wszystkich autorów mieszkających w wyznaczonym stanu oraz listę napisanych przez tych autorów książek. Dla formowania zapytań SQL do bazy danych wykorzystaj następne tabeli bazy danych: authors, titleautor, titles. Parametrem wejściowym procedury musi być stan zamieszkania autorów. Rezultat procedury musi zawierać trzy kolumny: Imię, Nazwisko autora oraz Tytuł książki. Napisz kod dla uruchomienia procedury. Za dopomogą procedury systemowej sp_helptext sprawdź kod źródłowy stworzonej procedury. 2.3.Do poprzedniej procedury dodaj nową procedurę grupy. Procedura musi realizować poszukiwanie książek , cena których jest w wyznaczonym zasięgu. Parametrami wejściowymi procedury są: typ książki, nakład, cena dolna, cena górna. Dla formowania zapytań SQL do bazy danych wykorzystaj tabeli titles oraz publishers. Rezultat procedury musi zawierać dwie kolumny: tytuł książki oraz wydawnictwo. Napisz kod dla uruchomienia procedury. 2.4.Utwórz procedurę „get_sum” , zwracającą sumę cen wszystkich sprzedanych książek napisanych przez autorzy, mieszkające w wyznaczonym stanu. Procedura musi mieć dwa parametry wejściowe oraz jeden wyjściowy. Parametrami wejściowymi są stan i typy książek(odpowiedni pola „state” i „type” w tabelach). Parametr wyjściowy musi zawierać rezultat – sumę cen sprzedanych książek. Dla formowania zapytań SQL wykorzystaj tabeli authors, titleauthor, titles. Zapytanie SQL musi ewentualne zawierać funkcję agregowania SUM. Ciało procedury musi sprawdzać czy nie są puste parametry wejściowe. 6 Kiedy te parametry są puste suma musi być wyznaczona na wszystkich sprzedanych książkach. Napisz kod dla uruchomienia procedury. 2.5. Za dopomogą procedury sp_depends wyznacz wszystkie obiekty bazy danych którzy są skojarzone ze stworzonymi procedurami. 2.6. Utwórz kursor „aut_curs” który musi odwzorować z tabeli authors dani autorów zamieszkanych w wyznaczonym stanie. Stan zamieszkania(wartość kolumny state) może być nadany przez zmienną lokalną. Kursor musi być dwukierunkowym (typu „Scroll”) oraz odwzorować dla każdego rekordu następne elementy: stan zmiennej lokalnej w moment aktywacji kursora, identyfikator autora(„au_id”) ,nazwisko autora („au_fname”), oraz stan zamieszkania(„state”). Napisz kod dla uaktualnienia, zamknięcia i usunięcia kursora, linie kodu do ustalenia wskaźnika na pierwszą pozycję, do przesunięcia wskaźnika do kolejnego wierszu. Sprawdź działanie kodu przy przesuwaniu wskaźnika po wierszach kursora. 2.7. Do kodu stworzonego w p. 2.6 dodaj linię kodu, który modyfikuje zmienną lokalną po odtworzeniu kursora. Popatrz czy zostali wprowadzone te zmiany do otwartego już kursora? Objaśnij ten rezultat. 2.8. Utwórz procedurę do stworzenia i odtwarzania kursora. Zanalizuj HELP z hasłem „CREATE PROCEDURE” oraz napisz kod do stworzenia i odtwarzania kursora. Procedura musi zawierać parametr wyjściowy typu kursor oraz uruchamiać sam kursor w tej zmiennej. Kursor musi zawierać kolumny: title_id, title i price z tabeli titles. Napisz kod do uruchomienia procedury, aktualizacji kursora, nawigacji po wierszach kursora, zamknięcia i usunięcia kursora. Sprawdź działanie kodu . 2.9. Uruchom procedurę systemową sp_cursor_list dla odczytania informacji o aktualnych kursorach. Dla sprawdzenia tej procedury uruchom bez zamknięcia kursor w p.2.6. Zanalizuj rezultaty. 3. Pytania kontrolne. 3.1. Jak zadeklarować zmienne lokalne, a jak globalne. 3.2. Co to jest blok instrukcji. Do czego służy. 3.3. Do czego służą instrukcje BREAK i CONTINUE w pętli WHILE. 3.4. Czy gdzieś w bazie danych jest przechowywany kod procedur składowanych. Jeśli tak to gdzie. 3.5. Co to jest i do czego służy kursor. Jakich instrukcji używa się do ich obsługi. 3.6. Jakiej funkcji można użyć do generowania błędów, jakie są jej parametry. 3.7. Co oznaczają opcje RECOMPILE i ENCRYPTION w definicji procedury. 7