Rozdział 6

PL/pgSQL

PL/pgSQL (Procedural Language/postgresSQL) rozszerza SQL o typowe konstrukcje spotykane w proceduralnych jezykach imperatywnych. Podstawową jednostką w PL/SQL jest blok, programy buduje się z zagnieżdżonych bloków.

W blokach języka PL/pgSQL wolno używać instukcji SQL służących do manipulowania danymi (np. SELECT, INSERT, UPDATE, DELETE) oraz instrukcji sterowania transakcjami. Dla instrukcji SELECT jest używana postać rozszerzona, pozwalająca umieszczać wyszukane wartości w zmiennych PL/pgSQL. Instrukcje definiowania danych, takie jak CREATE, DROP czy ALTER, nie są dozwolone.

Poza tym w blokach można używać typowych instrukcji takich jak przypisanie, instrukcja warunkowa, pętle, wywołania procedur.

Bloki PL/pgSQL umieszcza się w treści funkcji, które można uruchamiać zwykłą instrukcją SELECT z poziomu programu psql.

6.1  Funkcje

Funkcje definiuje się w Postgresie używając konstrukcji

CREATE FUNCTION nazwa(typ-parametru, ...)
RETURNS typ-wyniku AS $$
  treść-funkcji
$$ LANGUAGE nazwa-języka;

W definicji nie określa się nazw parametrów, odwołania do parametrów w treści funkcji mają postać $1, $2 itd.

Treść funkcji w starszych wersjach Postgresa otaczało się apostrofami, było to jednak niewygodne, bo wymagało dublowania wszystkich apostrofów wewnątrz treści.

Ponieważ w Postgresie polecenia SELECT można używać do obliczania wyrażeń nie związanych z bazą danych, np.

bd> SELECT 2 + 5 AS siedem;
siedem
------
7

więc najprostsze funkcje można definiować w SQL:

CREATE FUNCTION dodaj7(int4) RETURNS int4 AS $$
  SELECT $1 + 7;
$$ LANGUAGE sql;

Przykład wywołania takiej funkcji:

bd> SELECT dodaj7(4) AS wynik;
wynik
-----
11

Treść funkcji w SQL to ciąg poleceń SQL.

Typami parametrów mogą być nazwy tabel:

CREATE FUNCTION superwaga(Zwierz) RETURNS integer AS $$
  SELECT $1.waga * 2 AS waga;
$$ LANGUAGE sql;

i można się do takich funkcji odwoływać następująco:

bd> SELECT imie, superwaga(zwierz) FROM zwierz;
     imie     | superwaga 
--------------+------------
 Misza        |        248
 Lonia        |        270
 Toni         |        108
 Leszek       |        278
 Ania         |        270
 Zora         |        110
 Muszka       |         66
 ...

Jeśli argument jest typu złożonego można używać w odwołaniach do jego atrybutów notacji ,,kropkowej'', np. $1.emp.

Funkcje napisane w SQL zwracają jako wartość wynik ostatniego polecenia w treści, musi to być SELECT. Jeśli wynik nie ma być zbiorem, będzie to pierwszy wiersz z tego zapytania (warto więc zadbać o ewentualne ORDER BY ;-). Jeśli wynik zapytania był pusty, to jest zwracane NULL.

Jeśłi funkcja SQL ma zwracać zbiór, to typem wyniku musi być SETOF typ. Zwracane są wtedy wszystkie wiersze z ostatniego zapytania w treści.

Dla funkcji SQL, które nie zwraca nic rozsądnego typem wyniku powinno być void, nie może ona wtedy kończyć się zapytaniem SELECT.

CREATE FUNCTION oczyść () RETURNS void AS $$
  DELETE FROM Zwierz 
  WHERE waga <= 0;
$$ LANGUAGE SQL;

SELECT oczyśc();

6.1.1  Funkcje tabelowe

Funkcje tabelowe są to funkcje zwracające zbiory wierszy, ich wywołania umieszcza się w klauzulach FROM zapytań.

CREATE TABLE foo (fooid int, foosubid int, fooname text);

CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$
    SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;

SELECT * FROM getfoo(1) AS t1;

SELECT * FROM foo
WHERE foosubid in (select foosubid from getfoo(foo.fooid) z
                   where z.fooid = foo.fooid);

CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;

Normalnie jednak procedury bazy danych definiuje się nie w SQL, lecz w językach proceduralnych. Takim językiem dla serwera bazy danych w Postgresie jest najczęściej PL/pgSQL.

6.2  Instalacja języka PL/pgSQL

Domyślnie na serwerze instalowane są tylko języki C i SQL. Inne (w tym PL/pgSQL) trzeba instalować samemu. Odbywa się to w 3 krokach:

  1. Moduł obsługi języka (,,handler'') musi mieć postać biblioteki dzielonej, najlepiej umieszczonej w katalogu bibliotecznym Postgresa. Dla PL/pgSQL jest to zwykle

    /usr/local/pgsql/lib/plpgsql.so
  2. Moduł obsługi należy zadeklarować poleceniem

    CREATE FUNCTION nazwa-modułu-obsługi ()
    RETURNS LANGUAGE_HANDLER AS 'pełna-nazwa-biblioteki'
    LANGUAGE 'C';
    

    Specjalny typ wyniku LANGUAGE_HANDLER przekazuje bazie danych informację, że funkcja nie zwraca żadnego ze zdefiniowanych w SQL typów i nie można jej bezpośrednio użyć w poleceniach SQL.

    Uwaga: dla zgodności ze starszymi wersjami zamiast LANGUAGE_HANDLER można użyć OPAQUE.

    Dla PL/pgSQL może to wyglądać następująco

    CREATE FUNCTION plpgsql_call_handler ()
    RETURNS LANGUAGE_HANDLER AS '/usr/local/pgsql/lib/plpgsql.so'
    LANGUAGE 'C';
    

  3. Język programowania deklaruje się poleceniem

    CREATE [TRUSTED] [PROCEDURAL] LANGUAGE nazwa
    HANDLER nazwa-modułu-obsługi;
    

    Polecenie to nie występuje w SQL-92. Opcjonalne słowo TRUSTED oznacza, że zwykli użytkownicy mogą definiować własne funkcje i procedury wyzwalaczy. Nazwa może być podana bezpośrednio lub jako napis (tzn. w apostrofach).

    PL/pgSQL deklarujemy więc następujaco

    CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql
    HANDLER plpgsql_call_handler;
    

Dla standardowych języków dystrybucji istnieje skrypt createlang, wykonujący te same czynności. Aby zainstalować PL/pgSQL w bazie danych template1 wystarczy użyć

$ createlang plpgsql template1

Podczas tworzenia nowej bazy danych dziedziczy ona zainstalowane języki z wzorcowej bazy (domyślnie jest nią template1). Dlatego administratorzy często instalują potrzebne języki we wzorcowej bazie danej instalacji. Informacje o zainstalowanych językach proceduralnych znajdują się w katalogowej tabeli pg_language

Do usuwania języka proceduralnego służy polecenia DROP LANGUAGE albo skrypt droplang.

6.3  Bloki

Blok jest podstawową konstrukcją języka PL/pgSQL. Składnia bloku:

[DECLARE
  deklaracje zmiennych, stałych i procedur lokalnych>]
BEGIN
  instrukcje
END;

(nawiasy kwadratowe oznaczają część opcjonalną, nie są elementem składni.)

Przykład:

DECLARE
  a NUMERIC(5);
BEGIN
  SELECT COUNT(*) INTO a
  FROM EMP
  WHERE ENAME LIKE 'A%';
  IF a > 0 THEN
    INSERT INTO TEMP VALUES(a);
  END IF;
  RAISE NOTICE 'OK';
END; 

Oprócz komentarzy SQL (--) można używać komentarzy blokowych w stylu języka C (/* ... */).

6.4  Zmienne i typy

Transmisja informacji między programami w PL/pgSQL i bazami danych odbywa się za pośrednictwem zmienych. Z każdą zmienną jest związany jej typ. Typ zmiennej może być:

Najczęściej używanym typem generycznym jest NUMERIC. W zmiennych typu NUMERIC można przechowywać liczby całkowite lub rzeczywiste. Dla napisów najczęściej używa się typu VARCHAR(n), gdzie n jest maksymalna długością napisu. Przykład:

DECLARE
  cena NUMERIC;
  piwo VARCHAR(20);

Ponieważ zmienne PL/pgSQL często służa do operowania na danych pochodzących z kolumn tabel, muszą one w takim przypadku być z nimi dokładnie zgodne co do typu. Najłatwiej osiągnąć to używając w ich deklaracjach operatora %TYPE, na przykład:

DECLARE
  piwo Piwa.nazwa%TYPE;

nadaje zmiennej piwo taki typ, jaki został zadeklarowany dla kolumny nazwa podczas tworzenia tabeli Piwa.

Typ rekordowy jest typem złożonym z kilku nazwanych pól. Dla zmiennych tego typu można nie ustalać nazw pól używając deklaratora RECORD. Robi się tak czasem w procedurach wyzwalaczy, gdy mogą one być wołane dla różnych tabel. Częściej jednak typ zmiennej rekordowej określa się jako składający się z pól odpowiadających kolumnom określonej tabeli. Podaje się wtedy deklarator %ROWTYPE po nazwie tabeli, na przykład

DECLARE
  wiersz RECORD;
  opis_piwa Piwa%ROWTYPE;

jeśli tabela Piwa ma schemat Piwa(nazwa, producent, bar), to zmienna opis_piwa będzie składać się z pól opis_piwa.nazwa, opis_piwa.producent oraz opis_piwa.bar.

Niezależnie od typu początkową wartością każdej zmiennej jest NULL. Można jednak nadać im inną wartość początkową już w deklaracji, używając operatora przypisania :=. Oczywiście w treści funkcji mogą także wystąpić instrukcje przypisania, na przykład:

DECLARE
  a NUMERIC := 3;
BEGIN
  a := a + 1;
END;

6.5  Konstrukcje sterujące

Instrukcja warunkowa:

IF warunek THEN
  instrukcje1
[ELSE
  instrukcje2]
END IF;

Przykład:

IF b <> 0 THEN
  a := 1 / b;
ELSE
  a := 0;
END IF;

Bardziej rozbudowany przykład (z warunkowymi wstawieniami do tabeli):

DECLARE
    a NUMERIC;
    b NUMERIC;
BEGIN
    SELECT e,f INTO a,b FROM T1 WHERE e>1;
    IF b=1 THEN
        INSERT INTO T1 VALUES(b,a);
    ELSE
        INSERT INTO T1 VALUES(b+10,a+10);
    END IF;
END;

Pętle tworzy się konstrukcją LOOP:

[etykieta-pętli]
LOOP
  ...
  EXIT [etykieta-pętli] [WHEN warunek]
 ...
END LOOP [etykieta-pętli];

Instrukcja EXIT służy do wychodzenia z pętli (ewentualnie tylko gdy warunek jest spełniony). Połączona z etykietą pozwala wychodzić z pętli zagnieżdżonych.

Przykład:

<<zewn>>
LOOP
  LOOP
    EXIT; -- wychodzi z petli wewnętrznej
    EXIT zewn; -- wychodzi z obu pętli
  END LOOP;
END LOOP zewn;

Przykład na wstawienie par od (1, 1) do (100, 100) do tabeli T1:

DECLARE
  i NUMERIC := 1;
BEGIN
  LOOP
    INSERT INTO T1 VALUES(i,i);
    i := i+1;
    EXIT WHEN i>100;
  END LOOP;
END;

Inne kostrukcje sterujące uzyskuje się odpowiednio poprzedzając instrukcję LOOP.

Pętla WHILE ma skłdnię:

[etykieta-pętli]
WHILE warunek LOOP
  instrukcja ...
END LOOP;

zaś pętla FOR:

FOR zmienna IN [REVERSE] wyrażenie..wyrażenie LOOP
  instrukcja ...
END LOOP;

Przykład:

k:=5;
FOR i IN 1..k LOOP          
  RAISE NOTICE '#';
END LOOP;

Zmienna sterująca jest lokalna w pętli i nie musi być deklarowana, ale wtedy jest dostępna tylko wewnątrz pętli.

6.6  Instrukcje SQL w PL/pgSQL

Instrukcje DML (SELECT, UPDATE, DELETE, INSERT) można umieszczać bezpośrednio w kodzie SQL.

Instrukcja SELECT w programach musi zawierać frazę INTO, w przeciwnym razie (gdy wykonujemy ją tylko dla efektu ubocznego, np. wołając jakąś funkcję) należy początkowy znacznik SELECT zastąpić przez PERFORM.

6.6.1  SELECT INTO

Gdy mamy pewność, że instrukcja SELECT zwróci pojedynczą krotkę, można wyszukane atrybuty tej krotki umieścić w zmiennych, wg składni:

SELECT wyrażenie1,wyrażenie2,... INTO zmienna1,zmienna2,...
FROM ... WHERE ... itd.

np.

DECLARE
  b number(3);
  c number(3);
BEGIN
  SELECT COUNT(*),COUNT(a) INTO b,c FROM TEMP;
  IF (b > c) THEN
    RAISE NOTICE 'W kolumnie a występują wartości NULL';
  END IF;
END;

Do sprawdzania, czy zapytanie zwróciło jakiś wiersz, służy specjalna zmienna FOUND:

numer := 113;
SELECT * INTO prac FROM Pracownicy
WHERE nrprac = numer;
IF NOT FOUND THEN
  RAISE EXCEPTION 'Nie ma pracownika numer %.',numer;
END IF;

6.6.2  Kursory

Aby móc sensownie korzystać z zapytań zwracających bardziej (lub mniej) liczne zbiory krotek, należy użyć kursorów. Kursor jest ,,zmienną'' przebiegającą po krotkach relacji. Relacja może być tabelą, zwykle jednak jest wynikiem zapytania. Program może przetwarzać wynik zapytania, pobierając z kursora kolejne krotki relacji. Dla tabel można ponadto modyfikować lub usuwać bieżącą krotkę kursora.

Kursor można zwrócić jako wartość funkcji, a następnie użyć go w procedurze wywołującej do przeglądania wierszy. Jest to efektywna metoda zwracania dużych zbiorów wierszy z funkcji.

6.6.2.1  Deklarowanie kursorów

Dostęp do kursorów w PL/pgSQL odbywa się przez zmienne typu refcursor. Zmienne takie można deklarować bezpośrednio lub używając deklaracji kursora o składni:

  nazwa CURSOR [(argumenty)] FOR zapytanie;

(zamiast FOR można użyć IS dla zgodności z Oracle.) Opcjonalne argumenty to rozdzielona przecinkami lista par

nazwa typ
określająca parametry kursora -- ich nazwy występujące w zapytaniu zostaną zastąpione wartościami podawanymi podczas otwierania kursora.

Przykłady deklaracji kursorów:

DECLARE
  kurs1 refcursor;
  kurs2 CURSOR FOR SELECT * FROM Gatunki;
  kurs3 CURSOR (w integer) IS SELECT * FROM Zwierz WHERE wiek = w;

Wszystkie powyższe zmienne są typu refcursor. Pierwszej można użyć z dowolnym zapytaniem, drugiej tylko z zapytaniem związanym z nią, zaś ostatnia związana jest z parametryzownym zapytaniem (parametr w podczas otwierania kursora zostanie zastąpiony liczbą całkowitą).

6.6.2.2  Otwieranie kursora

Aby móc pobierać wiersze z kursora należy go otworzyć. Jeśli podczas deklarowania kursor nie został związany z żadnym zapytaniem, należy użyć OPEN FOR SELECT:

  OPEN niezwiązany-kursor FOR SELECT ...;

Otwarcie kursora oznacza przygotowanie zapytania do wykonania (wraz z ewentualnym podstawieniem dla zmiennych PL/pgSQL), np.

  OPEN kurs1 FOR SELECT * FROM Zwierz WHERE wiek = w;

Można także użyć OPEN FOR EXECUTE

  OPEN niezwiązany-kursor FOR EXECUTE napis;

aby móc dynamicznie konstruować zapytanie w postaci napisu, np.

  OPEN kurs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident($1);

Związany kursor otwiera się znacznie prościej

  OPEN związany-kursor [(argument,...)];

Argumenty podaje się tylko wtedy, jeśli podczas deklaracji użyto parametryzowanego zapytania. Przykłady:

  OPEN kurs2;
  OPEN kurs3(4);

6.6.2.3  Przechodzenie po kursorze

Po otwarciu kusora można go używać do pobierania wierszy z wyniku zapytania. Można go również zwrócić jako wartość funkcji, co ma skutek podobny do zwrócenia ,,tabeli''. Trzeba jednak pamiętac, że kursory są niejawnie zamykane na koniec transakcji.

Instrukcją FETCH pobieramy następny wiersz z kursora

  FETCH kursor INTO bufor;

Buforem może być zmienna wierszowa (%ROWTYPE), zmienna rekordowa lub rozdzielona przecinkami odpowiednia lista zmiennych prostych, podobnie jak dla SELECT INTO. Zmiennej FOUND można użyć do sprawdzenia, czy coś dostaliśmy:

  FETCH kurs1 INTO zwierzak;
  FETCH kurs2 INTO gat, kont, ochr, zjada;

Kursor zamykamy instrukcją CLOSE

  CLOSE cursor;

na przykład aby go zwolnić przed końcem transakcji

  CLOSE kurs1;

Poniższy przykład wypisuje imiona dwóch najcięższych zwierzaków o wadze nie przekraczającej 100 kg.

DECLARE
  CURSOR zwierzaki_wg_wagi(do_wagi number) FOR
      SELECT imie,waga
      FROM Zwierz 
      WHERE waga <= do_wagi
      ORDER BY waga DESC;
  imie Zwierz.imie%TYPE;
  waga Zwierz.waga%TYPE;
  zwierzak zwierzaki_wg_wagi%ROWTYPE;
BEGIN
  OPEN zwierzaki_wg_wagi(100);

  /* FETCH pobiera kolejny rekord z otwartego kursora. 
     W tym przypadku kolejne atrybuty pobranej krotki umieszczane 
     sa w kolejnych zmiennych */
  FETCH zwierzaki_wg_wagi into imie,waga; 

  /* Zmienna FOUND ma wartosc TRUE wtw, gdy ostatni FETCH zwrocil rekord */
  IF FOUND THEN
     RAISE NOTICE imie;
     /* Tu cala krotka jest umieszczana w zmiennej 'zwierzak' */
     FETCH zwierzaki_wg_wagi into zwierzak; 
     IF FOUND THEN
        RAISE NOTICE zwierzak.imie;
     END IF;
  END IF;
  CLOSE zwierzaki_wg_wagi;  -- po uzyciu kursor trzeba zamknac
END;

A oto inny przykład. Z tabeli T1(e,f), zawierającej pary liczb całkowitych, usuwamy wszystkie krotki z pierwszym elementem mniejszym od drugiego, zamiast nich wstawiając krotki z odwróconymi elementami.

DECLARE
  /* Zmienne na wynik zapytania: */
  a T1.e%TYPE;
  b T1.f%TYPE;
  CURSOR T1Cursor FOR
    SELECT e, f
    FROM T1
    WHERE e < f
    FOR UPDATE;
BEGIN
  OPEN T1Cursor;
  LOOP
    -- Pobieranie kolejnego wiersza z kursora
    FETCH T1Cursor INTO a, b;
    -- Kończymy gdy brak wierszy
    EXIT WHEN NOT FOUND;
    IF a < b THEN
      -- Usuwamy bieżącą krotkę
      DELETE FROM T1 WHERE CURRENT OF T1Cursor;
      -- Wstawiamy odwrócona krotkę
      INSERT INTO T1 VALUES(b, a);
    END IF;
  END LOOP;
  -- Zwalniamy kursor
  CLOSE T1Cursor;
END;

6.6.2.4  Zwracanie kursora

Funkcje PL/pgSQL mogą jako swoja wartość zwracać otwarty kursor. Funkcja wywołująca może wtedy pobierać wiersze z tego kursora

CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');

CREATE FUNCTION reffunc2() RETURNS refcursor AS $$
DECLARE
  ref refcursor;
BEGIN
  OPEN ref FOR SELECT col FROM test;
  RETURN ref;
END;
$$ LANGUAGE plpgsql;

SELECT reffunc2();
  
      reffunc2      
--------------------
 <unnamed cursor 1>
(1 row)

FETCH ALL IN "<unnamed cursor 1>";
COMMIT;

6.6.2.5  Pętla FOR z kursorem

Zamiast bezpośrednio używać kursorów można skorzystać ze specjalnej postaci pętli FOR.

Składnia:

DECLARE 
  <zmienna rekordowa> RECORD;
  ...
BEGIN
  ...
  FOR <zmienna rekordowa> IN SELECT ... LOOP
    ...
  END LOOP;

Instrukcje w pętli wykonują się po kolei dla każdego rekordu zwracanego przez kursor. Pętla otwiera i zamyka kursor automatycznie, nawet przy skoku na zewnątrz pętli (exit,goto) lub zgłoszeniu wyjątku.

W poniższym przykładzie wypisujemy wszystkie wagi zwierzaków nie większe niż 200 kg.

  FOR p IN SELECT imie,waga
           FROM Zwierz 
           WHERE waga <= 200
           ORDER BY waga DESC
  LOOP
    RAISE NOTICE '%', p.waga;
  END LOOP;

6.7  Funkcje

Funkcje tworzymy poleceniem CREATE FUNCTION. Składnia:

CREATE [OR REPLACE] FUNCTION <nazwa funkcji> ([<lista parametrów>])
       RETURNS <typ wyniku> AS $$
DECLARE
  ...
BEGIN
  ...
END;
$$ LANGUAGE 'plpgsql';

Parametry deklaruje się podając ich typy. MOżna również podać ich nazwy. Jeśłi tego nie zrobimy, to w treści odwołujemy się do nich przez $1, $2 ..., najlepiej nazywając je deklaracją ALIAS w sekcji DECLARE

DECLARE
  subtotal ALIAS FOR $1;
  ...

Przykład (tworzy funkcję samodzielną, przechowywaną w bazie danych):

-- funkcja liczy sume zarobkow w zadanym dziale
CREATE OR REPLACE FUNCTION suma_zarobkow(dzial varchar2)
  RETURNS number AS $$
DECLARE
  suma number(6);
BEGIN
  SELECT sum(sal) INTO suma
  FROM EMP
  WHERE deptno = (select deptno from dept where dname = dzial);
  RETURN suma;
END;
$$ LANGUAGE 'plpgsql';

Tak zdefiniowanej funkcji można używać np. w zapytaniach.

Klauzula OR REPLACE powoduje, że istniejąca specyfikacja zostanie zastąpiona nową.

Funkcje usuwa się instrukcją:

DROP FUNCTION nazwa-funkcji (lista-typów-parametrów);

Podanie listy typów jest konieczne, ponieważ można deklarować funkcje polimorficzne.

6.7.1  Zwracanie wartości

RETURN wyrażenie;

RETURN z wyrażeniem służy do zwracania wartościz funkcji prostej (tzn. takiej, która nie zwraca zbioru). Wywołanie funkcji kończy się i wartość wyrażenia jest zwracana do kontekstu wywołującego. Wartość wyrażenia zostanie automatycznie dostosowana do typu funkcji (o ile to możliwe). Jeśli typem funkcji jest void, to można pominąć wyrażenie, zresztą jego wartość i tak będzie zignorowana.

RETURN NEXT wyrażenie;

Jeśli typem funkcji jest SETOF sometype, wartości zwracane określa się instrukcjami RETURN NEXT. Nie powodują one zakończenia obliczania funkcji, lecz jedynie ,,budują'' wynik. Dopiero instrukcja RETURN bez argumentów powoduje zakończenie funkcji. Funkcje tego typu należy traktować jako zwracające ,,tabele'' i umieszczać ich wywołania we frazie FROM:

SELECT * FROM spora_funkcja();

6.8  Wyjątki

Błędy sygnalizujemy używając instrukcji

RAISE EXCEPTION '...';

Powoduje ona wypisanie komunikatu i wycofanie bieżącej transakcji.

6.9  Wyzwalacze

Wyzwalacze są specjalną konstrukcją PL/SQL, będącą rozszerzeniem procedur. Podczas gdy procedura musi być wywołana jawnie (z innej procedury 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 procedur -- bezargumentowych funkcji zwracajacych specjalny typ TRIGGER (funkcja wyzwalacza otrzymuje dane za pośrednictwem struktury TriggerData, a nie przez zwykłe parametry funkcyjne). Procedur tych nie należy wywoływać bezpośrednio. Sa one wywoływane niejawnie przez wyzwalacz, ilekroć wystąpi zdarzenie z nim związane. Zdarzeniem może być próba wykonania jednej z operacji SQL INSERT, DELETE lub UPDATE. Dla wyzwalaczy określa się, czy ich procedury 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.

Argumenty wywołania w definicji wyzwalacza powinny być literałami. Dzięki nim mozna tej samej funkcji używać w różnych wyzwalaczach. Najprostszy przykład to funkcja wpisująca informacje do dodatkowej tabeli zawierającej dziennik modyfikacji, gdzie parametrem mogłaby być nazwa modyfikowanej tabeli.

Funkcja wyzwalacza musi zostać zdefiniowana przed utworzeniem wyzwalacza.

Deklaracja wyzwalacza w PL/pgSQL ma następującą składnię:

CREATE TRIGGER nazwa
  BEFORE | AFTER INSERT | DELETE | UPDATE [OR ...] ON tabela
  [FOR EACH ROW | STATEMENT]
  EXECUTE PROCEDURE nazwa-procedury(argumenty);

Dodatkowe uwagi:

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.

Pora na przykłady. Załóżmy, że mamy tabelę

CREATE TABLE Gatunki (
  nazwa VARCHAR(30) PRIMARY KEY,
  kontynent VARCHAR(11),
  chroniony BOOLEAN,
  przysmak VARCHAR(15)
);

Utworzymy wyzwalacz dbający, aby nazwa kontynentu rozpoczynała się dużą literą:

CREATE FUNCTION normkont () RETURNS TRIGGER AS $$
BEGIN
  IF NEW.kontynent IS NOT NULL THEN
    NEW.kontynent := lower(NEW.kontynent);
    NEW.kontynent := initcap(NEW.kontynent);
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

CREATE TRIGGER gatwyzw1
  BEFORE INSERT OR UPDATE ON Gatunki
  FOR EACH ROW
  EXECUTE PROCEDURE normkont();

Zwróćmy uwagę, ż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.

6.10  Czego nie ma w tych notatkach (a co dotyczy pośrednio lub bezpośrednio PL/pgSQL-a)

Dokładnego omówienia typów w PL/pgSQL.

Różnych szczegółów dotyczących omawianych tematów, które można znaleźć w dokumentacji.

Omówienia dynamicznego SQL-a (czyli jak wykonać zapytanie, którego treść nie jest znana w trakcie kompilacji).

Omówienia środowisk, w których działa PL/pgSQL (serwer, wstawki w C i innych językach).

6.11  Gdzie to można znaleźć

W dokumentacji on-line: www.postgresql.org/docs.

6.12  Uwagi

Jeśli kolumna zadeklarowana jest jako SERIAL, to wyglada na to, że podczas wstawiania otrzymuje ona wartość jeszcze przed odpalaniem ewentualnych wyzwalaczy typu BEFORE.

Pisząc procedury bazy danych trzeba pamiętać, że będą one wykonywane na serwerze, w środowisku innym niż środowisko klienta bazy danych. Pewne pliki mogą nie istnieć lub też być dostępne pod innymi nazwami.