home_site

Lab07 - Procedury składowane, PL/pgSQL [ ver. BD1.2025.11.17.002 ]

Zawartość strony

Plan zajęć

W ramach bazy danych PostgreSQL istnieje możliwość definiowania własnych funkcji. Do realizacji ich można wykorzystać język SQL lub wbudowany w bazę danych język PL/pgSQL. Możliwe jest także tworzenie funkcji z wykorzytsaniem języków proceduralnych tj. C, perl czy python. W tym przypadku przygotowane biblioteki dołączamy do bazy danych a poprzez polecenia DDL ( CREATE FUNCTION ) tworzymy odpowiednie obiekty w bazie danych z funkcjonalnością realizowaną przez zewnętrzne programy. W ramach zajęć przedstawione zostaną funkcje realizowane z wykorzystaniem języka SQL i PL/pgSQL. Podstawowa struktura polecenia tworzącego funkcję składowaną przedstawia poniższe polecenie.

CREATE FUNCTION nazwa ([typ_danych [, ]])
  RETURNS typ_wyniku
  AS definicja
  LANGUAGE nazwa_jezyka

Polecenia SQL i interfejsu psql pomagające przetwarzać opracowane funkcje.

-- Wyświetlenie funkcji użytkownika - polecenie z interfejsu psql
\df
-- Wyświetlenie funkcji użytkownika - polecenie SQL - tablica pg_proc
\d pg_proc
-- Polecenie SQL wyświetlające zawartość procedury
SELECT prosrc FROM pg_proc WHERE proname='nazwa_funkcji' ;
-- Polecenie modyfikujące procedurę lub je tworzące
CREATE OR REPLASE FUNCTION ...
-- Polecenie usuwające procedurę
DROP FUNCTION nazwa_funkcji([lista typów])

Procedury składowane w języku SQL

Funkcje w języku SQL w bazie KURS.

Funkcja wyświetlająca uczestników określonego kursu - realizacja zapytania parametrycznego.
CREATE OR REPLACE FUNCTION sql1 ( ikurs int )
RETURNS SETOF uczestnik AS
$$
    SELECT u.id_uczestnik, u.nazwisko, u.imie 
         FROM uczestnik u JOIN uczest_kurs uk ON u.id_uczestnik = uk.id_uczest
         WHERE uk.id_kurs = ikurs;
$$
LANGUAGE SQL;   
Wykorzystanie funkcji sql1(int) i jej usunięcie.
SELECT sql1(1) ;         -- funkcja w obszarze argumentów
SELECT * FROM sql(1);    -- funkcja w obszarze źródeł danych ( po FROM )

DROP FUNCTION sql1(int); -- usunięcie funkcji sql1(int)
  
Deklaracja typu zwracanych wyników, z parametrem i bez parametru SETOF.
CREATE OR REPLACE FUNCTION sql2a ( int, int ) 
RETURNS uczestnik AS
$$
   SELECT * FROM uczestnik WHERE id_uczestnik IN ( $1, $2 ) ;
$$
LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION sql2b ( int, int ) 
RETURNS SETOF uczestnik AS
$$
   SELECT * FROM uczestnik WHERE id_uczestnik IN ( $1, $2 ) ;
$$
LANGUAGE SQL;   

SELECT * FROM sql2a(1,2);  -- testowanie funkcji fun2a()
SELECT * FROM sql2b(1,2);  -- testowanie funkcji fun2b()

DROP FUNCTION sql2a(int,int);  -- usunięcie funkcji fun2a()
DROP FUNCTION sql2b(int,int);  -- usunięcie funkcji fun2b()
  

Procedury składowane w języku PL/pgSQL

Pierwsza funkcja w języku pgSQL.
CREATE FUNCTION fun1 (m integer, n integer) 
RETURNS integer AS 
$$                   -- otwarcie bloku programowego
  BEGIN
    RETURN m + n;
  END;                                                              
$$                   -- zamknięcie bloku programowego
LANGUAGE plpgsql;    -- deklaracja języka
  
Funkcja zwracająca nazwisko uczestnika o zadanym id. Typ zmiennej przypisany do typu atrybut w tabeli.
CREATE OR REPLACE FUNCTION fun2 ( int ) RETURNS text AS 
$$
  DECLARE
     iducz ALIAS FOR $1;                 -- przypisanie atrybutu do parametru
     name uczestnik.nazwisko%TYPE;       -- przypisanie typu atrybutu do zmiennej 
  BEGIN
     SELECT INTO name nazwisko FROM uczestnik 
	 WHERE id_uczestnik = iducz ;
     RETURN name;
  END;
$$ LANGUAGE 'plpgsql';
  
Funkcja zwracająca imię i nazwisko uczestnika o zadanym id. Typ zmiennej przypisany do typu rekordu w tabeli.
CREATE OR REPLACE FUNCTION fun2a ( int ) 
RETURNS text AS 
$$
  DECLARE
     iducz ALIAS FOR $1;
     name uczestnik%ROWTYPE;             -- przypisanie typu atrybutu do typu rekordu  
  BEGIN
     SELECT * INTO name FROM uczestnik 
	 WHERE id_uczestnik = iducz ;
     RETURN name.imie || ' ' || name.nazwisko;
  END;
$$ LANGUAGE 'plpgsql';

SELECT fun2a(1) ;
  
Funkcja zwracająca imię i nazwisko uczestnika o zadanym id. Typ zmiennej przypisany do typu RECORD.
CREATE OR REPLACE FUNCTION fun2b ( int ) 
RETURNS text AS 
$$
  DECLARE
     iducz ALIAS FOR $1;
     name RECORD;                        -- przypisanie typu RECORD
  BEGIN
     SELECT  imie, nazwisko INTO name FROM uczestnik 
	 WHERE id_uczestnik = iducz ;
     RETURN name.imie || ' ' || name.nazwisko;
  END;
$$ LANGUAGE 'plpgsql';

SELECT fun2b(1) ;
  

Obsługa błędów w PL/pgSQL - RAISE

Obsługa błędów jest ważnym elementem procedur działających w ramach bazy danych. W ramach obsługi funkcji mamy możliwość wykorzystania polecenia RAISE, które udostępnia przekazzanie informacji do użytkownika jak i do systemowego logu. Dodatkowo istnieje możliwość przestałania do użytkownia standardowego kodu błedu w ramach parametru SQLSTATE.

Realizacja funkcjanalności obsługi błędów - RAISE.

Wyświetlenie uczestnika kursu, w przypadku braku rekordu sygnalizacja błędu.
CREATE OR REPLACE FUNCTION fun4 (iducz int) RETURNS text AS $$
DECLARE
--    iducz int;
    rec_uczestnik uczestnik%ROWTYPE;
BEGIN
    SELECT INTO rec_uczestnik *  FROM uczestnik WHERE id_uczestnik = iducz;
    IF NOT FOUND THEN
      RAISE EXCEPTION 'Uczestnik % nie jest w bazie', iducz;
    END IF;
    RETURN  rec_uczestnik.imie || ' ' || rec_uczestnik.nazwisko;
END; 
$$ 
LANGUAGE 'plpgsql';
  

Pętle w języku PL/pgSQL

Poniżej przedstawione zostały konstrukcję pętli LOOP, WHILE i FOR w ramach języka PL/pgSQL.

Przykłady wykorzystania pętli w bazie danych KURS.

Lista uczestników wyszukiwanych atrybutem LIKE. Typ zwracanych danych TABLE.
CREATE OR REPLACE FUNCTION fun3 (p_pattern VARCHAR) 
 RETURNS TABLE ( im VARCHAR, naz VARCHAR ) AS            -- zwracany typ danych typu tablica 
$$
BEGIN
 RETURN QUERY 
   SELECT imie, nazwisko FROM uczestnik WHERE nazwisko LIKE p_pattern ;
END; 
$$ LANGUAGE 'plpgsql';

SELECT * FROM fun3('F%');
  
Modyfikacja danych, pętla LOOP.
CREATE OR REPLACE FUNCTION fun3a (p_pattern VARCHAR) 
 RETURNS TABLE ( im VARCHAR, naz VARCHAR ) AS 
$$
DECLARE 
    var_r RECORD;
BEGIN
 FOR var_r IN (SELECT imie, nazwisko FROM uczestnik  
                      WHERE nazwisko LIKE p_pattern )  
 LOOP
        im  := var_r.imie ; 
        naz := upper(var_r.nazwisko);
        RETURN NEXT;
 END LOOP;
END; 
$$  LANGUAGE 'plpgsql';

SELECT * FROM fun3a('F%');
  
Uczestnicy kursu, wybór sortowanie { 'U' - nazwisko, grupa, 'G' - grupa, nazwisko }, ustwienie liczby zwróconych wierszy. Wykorzystanie konstrukcji dynamicznej realizacji zapytań w ramach struktury konstrukcji EXECUTE ... USING.
CREATE OR REPLACE FUNCTION fun3b ( sort_type char(1), n INTEGER ) 
RETURNS TABLE ( im VARCHAR, naz VARCHAR, opis VARCHAR ) AS 
$$
DECLARE
    rec RECORD;
    query text;
BEGIN
    query := 'SELECT u.imie, u.nazwisko, ko.opis FROM uczestnik u JOIN uczest_kurs uk ON u.id_uczestnik=uk.id_uczest
                                                                  JOIN kurs k USING ( id_kurs )
                                                                  JOIN kurs_opis ko ON k.id_nazwa = ko.id_kurs ';
   IF sort_type = 'U' THEN
      query := query || 'ORDER BY u.nazwisko, ko.opis ';
   ELSIF sort_type = 'G' THEN
      query := query || 'ORDER BY ko.opis, u.nazwisko ';
   ELSE 
      RAISE EXCEPTION 'Niepoprawny typ sortowania %s', sort_type;
   END IF;
 
   query := query || ' LIMIT $1';
 
   FOR rec IN EXECUTE query USING n 
      LOOP
      -- RAISE NOTICE '% - %', rec.release_year, rec.title;
        im   := rec.imie   ; 
        naz  := rec.nazwisko  ;
        opis := rec.opis ;
        RETURN NEXT;
   END LOOP;
   
END;
$$ LANGUAGE plpgsql;  
  

Kursory w języku PL/pgSQL

Przykłady wykorzystania kursora w bazie KURS.

Odczyt danych z tablicy uczestnik.
CREATE OR REPLACE FUNCTION fun5 (stext text) RETURNS text AS 
$$
DECLARE 
  records TEXT DEFAULT '';
  rec_uczestnik   RECORD;
  cur_uczestnicy CURSOR FOR SELECT * FROM uczestnik ;
  id INTEGER;
BEGIN
   id := 0 ;
   OPEN cur_uczestnicy ;           -- otwarcie kursora
   LOOP
    -- pobranie rekordu z kursora do zmiennej rec_uczestnik
      FETCH cur_uczestnicy INTO rec_uczestnik;
      EXIT WHEN NOT FOUND;         -- zamkniecie jak brak dalszych rekordow
    -- tworzenie rekordu wynikowego 
      IF rec_uczestnik.nazwisko LIKE stext AND id != 0 THEN 
         records := records || ',' || rec_uczestnik.nazwisko || ':' || rec_uczestnik.imie;
      END IF;       
      IF rec_uczestnik.nazwisko LIKE stext AND id = 0 THEN 
         records := rec_uczestnik.nazwisko || ':' || rec_uczestnik.imie;
         id := 1 ;
      END IF;
     
   END LOOP;
   CLOSE cur_uczestnicy;           -- zamkniecie kursora 
   RETURN records;
END; 
$$ LANGUAGE plpgsql;