Procedury składowane są to procedury lub funkcje, przechowywane
na serwerze bazy danych i tam wykonywane. Część z nich jest dostarczona
z implementacją, na przykład sin()
.
Możemy jednak tworzyć własne procedury, na przykład żeby obudować ciąg czynności wykonywanych wspólnie i nie musieć pisać ich za każdym razem. Ale podstawowe przeznaczenie to pisanie wyzwalaczy -- procedur odpalanych automatycznie, ilekroć w bazie danych zajdzie określone zdarzenie. Do ich pisania najczęściej używa się języka PL/SQL, dlatego nim włąśnie będziemy się zajmować.
Zacznijmy jednak od zwykłych funkcji. Prostą funkcję można napisać w SQL. Przypominam składnię z notatek
CREATE FUNCTION nazwa (parametr typ-parametru, ...) RETURNS typ-wyniku AS $$ treść-funkcji $$ LANGUAGE nazwa-języka;
Ponieważ funkcje mogą być pisane w różnych językach, trzeba było
jakoś oznaczyć początek i koniec kodu. Cudzysłowy są tu fatalnym
rozwiązaniem, bo zwykle występują w treści. Wybrano $$
,
ale można te ograniczniki zastąpić innymi.
No to napiszmy funkcję liczącą, ile mamy zwierzaków cięższych niż podana waga.
CREATE OR REPLACE FUNCTION ile_ciezszych (ponad integer) RETURNS bigint AS $$ SELECT COUNT(*) FROM Zwierzaki WHERE waga > ponad; $$ LANGUAGE 'sql';
Żeby sprawdzić naszą funkcję, musimy jej użyć w zapytaniu SQL
bd=# select ile_ciezszych(10); ile_ciezszych --------------- 53 (1 wiersz)
Teraz coś naprawdę pożytecznego. Załóżmy, że tabela Zwierzaki
ma klucz sztuczny id
typu SERIAL
(zmieniona
definicja w pliku zoo-bis.sql.
Napiszemy funkcję, która wstawi nowy wiersz do tabeli i zwróci
automatycznie nadany identyfikator.
CREATE OR REPLACE FUNCTION nowy_zwierzak (imie VARCHAR, gatunek VARCHAR, wiek INT, waga INT) RETURNS integer AS $$ INSERT INTO Zwierzaki(imie, gatunek, wiek, waga) VALUES($1, $2, $3, $4) RETURNING id; $$ LANGUAGE 'sql';
Pora coś wstawić
bd=# select nowy_zwierzak('Kicia','tygrys',2,100); nowy_zwierzak --------------- 55 (1 wiersz)
To często jedyny sposób, żeby dowiedzieć się o nadany identyfikator.
Przy okazji poznaliśmy modyfikator RETURNING
, który zwraca
podane wartości (tak jak dla frazy SELECT
), można w ten sposób
otrzymać listę usuniętych wierszy:
DELETE FROM Zwierzaki WHERE gatunek = 'krowa' RETURNING *;
To jeszcze funkcja w PG/SQL, po podaniu liczby wierszy utworzy wiersze o kolejnych wartościach
CREATE OR REPLACE FUNCTION wstaw (ile integer) RETURNS integer AS $$ DECLARE i NUMERIC(3) := 1; BEGIN LOOP INSERT INTO T1 VALUES(i, i * i); i := i + 1; EXIT WHEN i > ile; END LOOP; RETURN i; END; $$ LANGUAGE 'plpgsql';
Wywołujemy podobnie
bd=# select wstaw(11); wstaw ------- 12 (1 wiersz)
Wyzwalacze są specjalną konstrukcją PL/SQL, będącą rozszerzeniem funkcja. Podczas gdy zwykła funkcja musi być wywołana jawnie (z innej funkcji lub instrukcji SELECT), wyzwalacze wywoływane są automatycznie zawsze wtedy, gdy wystąpią związane z nimi zdarzenia. Zdarzeniem jest modyfikacja tabeli.
Wyzwalacze w PL/pgSQL definiuje się używając uprzednio zdefiniowanych
funkcji bezargumentowych zwracajacych specjalny typ TRIGGER
.
Funkcja ta otrzymuje dane od wyzwalacza (za pośrednictwem struktury
TriggerData
, o której wolicie nie wiedzieć), a nie przez
zwykłe parametry funkcyjne. Nie należy takich funkcji wywoływać
bezpośrednio. Sa one wywoływane niejawnie przez stowarzyszony
wyzwalacz, ilekroć wystąpi zdarzenie z nim związane.
Funkcja wyzwalacza musi zostać zdefiniowana przed utworzeniem wyzwalacza.
Zdarzeniem może być próba wykonania jednej z operacji SQL
INSERT
, DELETE
lub UPDATE
.
Dla wyzwalaczy określa się też, czy ich funkcje mają być
wołane przed czy po właściwej operacji (BEFORE
lub
AFTER
).
Ponadto dla wyzwalacza określony jest jeden z dwóch poziomów: wiersza lub zdania. Wyzwalacz poziomu zdania jest odpalany tylko raz dla całego polecenia SQL, natomiast wyzwalacz poziomu wiersza jest odpalany niezależnie dla każdego modyfikowanego wiersza.
Składnia deklaracji wyzwalacza w PL/pgSQL jest następująca:
CREATE TRIGGER nazwa {BEFORE | AFTER} {INSERT | DELETE | UPDATE} [OR ...] ON tabela [FOR EACH {ROW | STATEMENT}] EXECUTE PROCEDURE nazwa-procedury(argumenty);
Dodatkowe wymagania:
OR
. Przykłady:
... INSERT ON R ... ... INSERT OR DELETE OR UPDATE ON R ...
FOR EACH ROW
określa, że wyzwalacz jest poziomu wiersza,
tzn. będzie odpalany osobno dla każdego zmienianego wiersza tabeli.
Domyślnie wyzwalacz jest poziomu całej instrukcji.
Usuwając wyzwalacz trzeba oprócz jego nazwy podać nazwę tabeli
DROP TRIGGER nazwa ON tabela;
W treści funkcji związanej z wyzwalaczem są dostępne dwie zmienne rekordowe
NEW
i OLD
, odnoszące się do nowej i starej zawartości
wiersza. Procedura wyzwalacza dla wstawiania i modyfikacji powinna zwracać
odpowiedni rekord, zwykle jest to po prostu NEW
. Można też
zwracać NULL, co w wyzwalaczach typu BEFORE
oznacza, że wykonanie
wyzwalacza nie powiodło się i operacja powinna zostać zignorowana.
Wyzwalacze tego samego typu uruchamia się w kolejności alfabetycznej według ich nazw. Jeśli w funkcji wyzwalacza używa się poleceń SQL, mogą one spowodować uruchomienie innych wyzwalaczy, nazywa się to kaskadowaniem wyzwalaczy. Nie istnieje żadne ograniczenie na liczbę poziomów wywołań kaskadowych, w szczególności możliwe są wywołania rekurencyjne. Programista jest odpowiedzialny za unikanie nieskończonej rekursji.
Pierwszy wyzwalacz napiszemy wspólnie. Będzie on tylko wypisywał ,,Tu jestem''. Po co pisać taki wyzwalacz? Otrzymamy poprawny składniowo schemat, który będziemy mogli modyfikować w kolejnych wyzwalaczach.
Wypisanie ,,Tu jestem'' pozwala stwierdzić, czy wyzwalacz w ogóle się odpalił.
Najpierw funkcja
CREATE FUNCTION pierwszy () RETURNS TRIGGER AS $$ BEGIN RAISE NOTICE 'tu jestem'; RETURN NEW; END; $$ LANGUAGE 'plpgsql';
Dwa wyjaśnienia:
Jeśli pracujemy z porządnego klienta, np. psql
, to
odbiera on taki komunikat i o wypisuje.
Pora na wyzwalacz
CREATE TRIGGER pierwszywyzw BEFORE INSERT OR UPDATE ON Gatunki FOR EACH ROW EXECUTE PROCEDURE pierwszy();
Pamiętajmy, że wykonanie polecenia CREATE TRIGGER
jedynie tworzy
wyzwalacz nie wykonując go. Aby uruchomić wyzwalacz należy spowodować
zajście odpowiedniego zdarzenia, w tym przypadku wstawienia lub modyfikacji
wiersza dla tabeli Gatunki
.
bd=# select * from gatunki; bd=# insert into Gatunki values ('krowa','nie','Europa','siano'); UWAGA: tu jestem INSERT 0 1
bd=# insert into Gatunki values ('krowa','nie','eUrOpA','siano'); bd=# select * from gatunki; ... krowa nie Europa siano ...
Najlepiej użyć RAISE EXCEPTION, powodującego oprócz wypisania komunikatu wycofanie transakcji. Lepiej to robić wyzwalaczem AFTER, błędna akcja zostanie porzucona. Wyzwalacze są wykonywane w jednej transakcji ze swoim zdarzeniem, czyli operacją modyfikacji.