Vytváření uživatelských funkcí
O vytvárení funkcí by se mohla napsat samostatná kniha, neboť se v něm, krom jiného, využívá programování. Nechci se pouštět do vysvětování programování (od toho tu jsou jiné tutoriály), proto uvedu jen pár jednoduchých příkladů vytvoření funkce a pokud vás to zaujme, můžete si zbytek dostudovat z oficiální dokumentace.
Toto je (prozatím?) poslední kapitola, takže vám přeji hodně zábavy při využívání všech vašich nabytých znalostí.
CREATE FUNCTION
Funkce (přesněji user defined functions) se v PostgreSQL vytvářejí příkazem CREATE FUNCTION
.
Zjednodušená syntaxe příkazu je následující:
Příkazem CREATE FUNCTION jmeno_funkce
vytvoříte
funkci jen v případě, že funkce tohoto jména ještě neexistuje.
Pokud použijete příkaz CREATE OR REPLACE FUNCTION jmeno_funkce
,
bude existující funkce stejného jména přepsána. (Přepsat můžete jenom funkce vytvořené pomocí CREATE
FUNCTION
, ne standardní funkce postgresu).
K čemu slouží jmeno_funkce je snad jasné. Argument (nebo argumenty) funkce se předávají při použití funkce v závorce, oddělené čárkou. Během deklarace funkce musíte určit typ_argumentu. (Například real, string atp.) Funkce nemusí mít žádné argumenty, pak se při deklaraci a použití funkce musí použít prázdné závorky.
Za závorkami následuje klíčové slovo RETURNS
a typ
návratové hodnoty funkce (real, string atp.). K tomu, k čemu je návratová hodnota, se ještě
dostanu (programátoři jistě vědí).
Za klíčovým slovem AS
je uvedeno tělo
funkce. Tělo funkce je v podstatě popis činnosti, kterou má funkce
vykonávat. Jak takový popis činosti vypadá, záleží na programovacím jazyku, který
k popisu činosti použijete. (V PostgreSQL máte více možností).
Tělo funkce se zadává jako string (fakt, nekecám, taky mě to překvapilo),
proto musí být uzavřené v apostrofech (nebo do čeho se text dá uzavřít).
Za klíčovým slovem LANGUAGE
je název jazyka,
který byl použit pro definici těla funkce. Asi vás už napadlo, že je tomu
tak proto, že těch jazyků je více. Například 'internal', C, SQL, PL/pgSQL
a další.
Běžný uživatel má k dispozici jazyk SQL popsaný níže.
Funkce lze přetěžovat. To znamená, že je možné vytvořit více funkcí stejného jména, které se ovšem liší počtem a/nebo typem argumentů.
DROP FUNCTION
Protože je možné funkce přetěžovat, musí se příkazu
DROP FUNCTION
předat nejen název funkce,
ale i typy argumentů, aby podle nich mohl jednoznačně identifkovat správnou
verzi funkce ke smazání.
Samozřejmě můžete odstranit funkci jen pokud na to máte práva.
Můžete mazat jen uživatelské funkce (user defined functions), tedy
funkce vytvořené pomocí CREATE FUNCTION
, nikoliv
interní funkce
Postgresu.
Smaže se ta funkce zadaného jména, která má shodný počet a typ argumetnů.
Jazyk SQL
Začnu příkladem. Vytvořím funkci pomocí jazyka SQL. To znamená,
že za klíčovým slovem LANGUAGE
bude jazyk SQL
a tělo funkce za klíčovým slovem AS
se bude skládat jen z příkazů SQL.
To je asi ten nejjednodušší způsob, jak napsat funkci :-).
Návratovou hodnotou (RETURN) funkce jazyka SQL je výsledek posledního SQL příkazu.
Funkce, kterou vytvořím, bude pouze vracet součet dvou čísel typu integer (Tedy návratová hodnota bude typu integer a argumenty funkce budou dvě čísla typu integer).
V příkladu si všiměte, jak se argumenty v tělu funkce používají. První argument je označen $1, druhý $2 atd.
Návratovou hodnotou je výsledek posledního (v příkladu jediného) SELECTu.
Další ukázka je převzata z dokumentace PostgreSQL. Všiměte si, že místo apostrofů je tělo funkce uzavřeno v $$. (To je v PostgreSQL další způsob, jak je možné ohraničovat text). To je preferovaný způsob, protože umožňuje v tělu funkce používat apostrofy bez nutnosti jejich escapování (pomocí zpětného lomítka).
Funkci se vám nepodaří vytvořit, pokud neexistuje tabulka bank. (Například
CREATE TABLE bank (accountno INT, balance NUMERIC);
)
Další informace o jazyku SQL najdete v dokumentaci.
Přetěžování funkcí
Funkci soucet z předchozího příkladu přetížím.
O funkci se říká že je přetížená, pokud existuje více funkcí stejného jména, které se odlišují počtem nebo typem argumentů. V příkladu se budou lišit typem argumentů.
Změna typu návratové hodnoty pro přetížení funkce nestačí. Při volání funkce je jasné, kolik a jakého typu jsou argumenty, ale návratová hodnota se z volání funkce odvodit nedá. Takže by se ani nedalo vybrat správnou verzi funkce.
Funkce se mohou volat následovně:
V prvním případě byla volána funkce soucet(integer,integer)
ve
druhém případě soucet(float, integer)
.
Protože není definovaná funkce
soucet(integer,float)
,
byla v třetím případě použita funkce soucet(integer, integer)
a číslo typu float (3.4) bylo Postgresem převedeno na typ integer (3).
Někdy by podobné automatické konverze mohli být nebezpečné,
proto je třeba si na to dávat pozor.
UPDATE: Nově se teď v Postgresu třetí volání (s nesprávnými datovými typy) vyvolá chyba. Argumenty musíte explicitně přetypovat:
Přetěžování funkcí funguje stejně i v dalších jazycích (PL/pgSQL atd.).
Funkce můžete vypsat pomocí metapříkazu \df
.
Jazyk PL/pgSQL
Jazyk SQL
je omezený pouze na SQL příkazy (SELECT,UPDATE, DELETE atp.).
Jazyk PL/pgSQL
naproti tomu umí takové věci, jako deklaraci proměnných, cykly,
podmínky, odchytávání výjimek.
Pokud se někdy rozhodnete napsat malinko složitější funkci, pravděpodobně sáhnete pro jazyku PL/pgSQL.
Struktura těla funkce v jazyku PL/pgSQL vypadá takto:
V části DECLARE
se deklarují proměnné, mezi BEGIN
a END
je pak
program. (BEGIN/END
v tomto případě nemají nic společného s transakcemi).
label
je libovolný název bloku, který můžete použít pro kvalifikaci proměnných pomcí tečkové notace.
-- nebo lépe SELECT suma(CAST('{3,3,5}' AS integer[]));
suma
------
11
(1 řádka)
Jazyk PL/Python
S pl/pgSQL byste si měli vystačit, ale někdy narazíte na to, že je to velice jednoduchý programovací jazyk a vám by se hodil nějaký komplexnější. K tomu můžete využít například perl, nebo python.
Abyste mohli použít jazyk PL/Python
, musíte si ho nejdřív pro danou databázi
„aktivovat“ příkazem CREATE EXTENSION
. (Musíte na to taky mít příslušná
práva).
Tohle na mě vyzkočilo, když jsem to zkoušel v OpenSUSE. Extension plpythyonu neexistovala, musel jsem si jí extra doinstalovat:
Teď už to šlo (pod superuživatelským účtem postgres):
Metapříkazem \dL
si můžete nechat vypsat aktivní jazyky:
Seznam jazyků
Jméno | Vlastník | Důvěryhodný | Popis
-----------+----------+-------------+------------------------------------------
plpgsql | postgres | t | PL/pgSQL procedural language
plpythonu | postgres | f | PL/PythonU untrusted procedural language
(2 řádky)
Jazyk plpythonu je označen jako nedůvěryhodný, takže jej běžný uživatel nemůže používat. Buď musí míst superuživatelská práva, nebo musíte změnit důveryhodnost jazyka na true.
Jako nedůvěryhodný je plpythonu označen proto, protože s ním může nekalý uživatel narušit běh serveru (například jeho vytížením). Když vím, že uživatelé mého serveru jsou důvěryhodní, změním plpythonu na důvěryhodný:
rimmer1=> SELECT * FROM pg_language WHERE lanname= 'plpythonu';
lanname | lanowner | lanispl | lanpltrusted | lanplcallfoid | laninline | lanvalidator | lanacl
-----------+----------+---------+--------------+---------------+-----------+--------------+--------
plpythonu | 10 | t | f | 74544 | 74545 | 74546 |
(1 řádka)
rimmer1=# UPDATE pg_language SET lanpltrusted = true WHERE lanname = 'plpythonu';
UPDATE 1
-- ted muze pouzivat python kazdy
Jinou možností by bylo nastavit jen konkrétnímu uživateli superuživatelská práva:
Příkaz DO
umožňuje spustit tělo funkce, bez toho, aby se
musela funkce definovat. Toho se dá využít pro ověření funkčnosti jazyka:
plpy
je vždy automaticky importovaný.
Obsahuje užitečné metody jako je info
, která zobrazí INFO hlášení (klasické pythonovské
metody pro výpis, jako je print
, nemůžete použít).
MySQL
V MySQL neexistují „jazyky“. Máte k dispozici jen jeden „jazyk“, který můžete
používat s CREATE FUNCTION
pro tělo funkce. Nejvíce se podobá postgresovskému
jazyku SQL – prostě jen spouštíte SQL příkazy. MySQL má ve svých SQL příkazech i různé
řídící konstrukce
(IF, LOOP, WHILE, RETURN) atp., které můžete používat ve funkcích a procedůrách, takže se psaní
funkcí vlastně více podobá jazyku pl/pgSQL.
MySQL má kromě CREATE FUNCTION
ještě CREATE PROCEDURE
.
Procedůra se volá pomocí CALL nazvev_procedury();
a nemá návratovou
hodnotu (může vracet hodnoty pomocí výstupních parametrů, ale to je nad rámec této kapitoly :-).
Funkce naproti tomu mohou vracet hodnotu, mohou být volány jako jakékoliv jiné funkce MySQL v SQL příkazech.
V MySQL jsou výstupem procedury všechny výstupy ze SELECTů spuštěných v těle procedury (ne jen ten poslední, jako
v Postgresu). Né každý klient se s tím dokáže poprat, takže se takovým opičárnám raději vyhněte.
Funkce vracejí hodnotu pomocí klíčového slova RETURN
.
V MySQL se nepíše tělo funkce jako textový řetězec, takže je trochu problém s ukončováním SQL příkazů v těle versus ukončení definice funkce. Obvykle totiž klient (program mysql), když narazí na středník, pošle příkaz serveru. To by způsobilo odeslání začátku funkce a prvního příkazu v těle funkce ukončeného středníkem. Zbytek funkce (resp. další příkazy v těle funkce ukončené středníkem) by se posílaly na server jako další nesouvisející příkazy. Tak tak to prostě nejde.
Obejít se to dá změnou ukončovače (DELIMITER) ze středníku na něco jiného. Obyvkle se používají lomítka
//
.
Argumenty funkce/procedury musí mít jména (v PostgreSQL mohou a nemusí).
Klíčové slovo DETERMINISTIC
říká, že funkce pro stejné argumenty
vrací vždy stejný výsledek. Další možností je NO DETERMINISTIC
. (Tato informace je důležitá pro
optimalizaci dotazů.)
MySQL nepodporuje konstrukci CREATE OR REPLACE FUNCTION;
. (Místo toho
umí DROP FUNCTION IF EXISTS
.)
V MySQL není možné funkce přetěžovat. Proto se při mazání funkce nemusí uvádět parametry.
Ještě ukázka vytvoření, použití a smazání procedury:
mysql> CREATE PROCEDURE soucet (p1 integer, p2 integer)
DETERMINISTIC
BEGIN
SELECT p1 + p2;
END
//
mysql> DELIMITER ;
myqsl> CALL soucet(3,-4);
+---------+
| p1 + p2 |
+---------+
| -1 |
+---------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> DROP PROCEDURE IF EXISTS soucet;
Query OK, 0 rows affected (0.00 sec)
SQLite
V SQLite nelze vytvořit uživatelské funkce pomocí CREATE FUNCTION
. Můžete si jen
doprogramovat funkce pomocí jazyka C, ale to už je nad rámec tohoto tutoriálu.
Oracle
Jazyk pl/pgSQL vychází z Oracle jazyka PL/SQL. Z toho vyplývá, že vytváření funkcí v Oracle je hodně podobné. Můžete se podívat do dokumentace PostgreSQL na Porting from Oracle PL/SQL, kde jsou popsány základní rozdíly mezi pl/pgSQL a PL/SQL.
Nějaké to programování v Oracle si můžete prohlédnout v kapitole o triggerech.
Teď jen ve stručnosti ukáži, jak by se daly v Oracle řešit příklady popsané v předchozí části o
PostgreSQL. Začnu funkcí soucet
.
Místo AS
se píše v Oracle IS
, místo RETURNS
je před IS
RETURN
, tělo se nezadává jako text, argumenty
mají jméno (i a j) a na konci se neurčuje použitý programovací jazyk,
protože v Oracle je jen jeden – PL/SQL.
Postgres taky umožňuje argumenty funkce pojmenovat. Přečtěte si dokumentaci :-).
Použití:
Funkce a procedury
Začněme přípravou tabulky a ukázkou funkce tp1
:
INSERT INTO bank VALUES(1000,1);
CREATE OR REPLACE FUNCTION tp1 (a integer, b numeric)
RETURN numeric
IS
r numeric;
BEGIN
UPDATE bank SET balance = balance - b
WHERE accountno = a;
SELECT balance
INTO r
FROM bank WHERE accountno = a;
RETURN r;
END;
/
Pokus o použití:
Oracle nepovoluje spouštět DML (Data modification language) příkazy ve funkcích, tedy v našem případě
UPDATE
, uvnitř jiného SQL dotazu. Je to něco kvůli transakcím, ale ono obecně je to blbý
nápad mít funkci s „side efektem“. Obejít se to dá tak, že funkci zavoláte
následujícím způsobem:
Funkce put_line
z balíčku DBMS_OUTPUT
vypíše svůj argument na obrazovku. Tedy
v případě, že je nastaven SERVEROUTPUT
na ON
.
Pokud chcete používat DML, většinou se na to v Oracle PL/SQL používají procedury.
Procedura je to samé jako funkce, jenom nemá návratovou hodnotu a volá se pomocí EXEC
(ne SELECTem).
Spuštění procedury pomocí EXEC
:
Mnohem hezčí, že? :-) Dokonce můžete místo EXEC
použít CALL
, jako v MySQL.
Pole
Pokud jde o práci s polem, v Oracle je potřeba si nejdřív definovat vlastní typ.
Následující příklad vytvoří datový typ se jménem intArrayTyp
, který
bude obsahovat až 200 hodnot typu integer.
Datový typ lze smazat příkazem DROP TYPE jmeno_typu;
LOOP
se používá v Oracle trochu jinak než v Postgresu,
jinak je funkce suma
hodně podobná:
S použitím už je to horší. V příkladu vidíte, jak se v oracle vytvoří typ (řádka 4), nastaví se, kolik může obsahovat hodnot (řádka 5) a pak se pole hodnotami naplní (řádky 6 a 7).
Pokud byste vynechali volání na řádce 5, nebo mu zkusili dát čílo větší než našich deklarovaných 200 položek, dojde k chybě.
Na výstup se vypíše 4.
Balíčky
Balíčky (packages) jsou databázové objekty, které seskupují funkce, procedury, typy, proměnné atp. Můžete si je představit jako třeba struktury v jazyku C, nebo record v Pascalu. V Postgresu balíčky neexistují, místo nich se doporučuje používat schema.
Důvod, proč se tu o balíčcích zmiňuji je hlavně ten, že v Oracle je možné přetěžovat (overload) funkce jen v nich.
Kromě balíčků, které si naprogramujete sami, existují balíčky, které jsou součástí Oracle od jeho instalace. S jedním z nich
už jste se setkali – s balíčekem DBMS_OUTPUT
, který obsahuje funkci put_line
(a spousty dalších).
Dalším takovým balíčkem je balíček STANDARD
. Ten má tu zvláštnost, že když chcete zavolat jeho funkce, nemusíte
je kvalifikovat jeho jménem. Takže například volání STANDARD.ABS(-5)
lze zapat jako ABS(-5)
.
Všechny objekty z balíčku STANDARD
můžete vypsat takto:
Všiměte si, že funkce ABS
je tam několikrát. To právě kvůli tomu, že je přetížená (existuje více verzí s různým počtem a druhem argumentů).
Kvůli optimalizaci rozděluje Oracle vytvoření balíčku do dvou kroků. V prvním se deklaruje, co balíček obsahuje.
Deklaruji tedy balíček matika
se dvěmi verzemi funkce soucet
.
V druhém kroku definuji těla funkcí (BODY).
Hurá. Teď už je jen použít:
Aha, tak to nevyšlo. Jen jsem 3x zavolal funkci soucet
, kterou jsem definoval dříve bez balíčku.
Nesmí se zapomenout na kvalifikaci názvem balíčku.
Tak bohužel, tohle v Oracle nepůjde. Oracle nedokáže rozlišit float od integeru. Přestože dovolil vytvořit funkce, které se od sebe lišily jen v těchto typech parametrů. No, ale abyste mi věřili, že se funkce dají přetěžovat, ukáži příklad s typem varchar2 místo float. Ten už snad od integer odliší.
function soucet (a integer,b integer) return integer;
function soucet (a varchar2, b integer) return number;
end;
/
CREATE OR REPLACE PACKAGE BODY matika AS
function soucet (a integer,b integer) return integer
is
begin
RETURN a + b;
end;
function soucet (a varchar2, b integer) return number
is
begin
RETURN TO_NUMBER(a) + b;
end;
END;
/
Chvilka napětí, jak to dopadne:
První pokus ve všech příkladech volal verzi funkce s objema parametry typu integer. Teď zkusím při druhém volání předat jako první parametr text. Nejdřív ale pro jistotu nastavím oddělovače desetinných míst a tisíců, aby náhodou Oracle neočekával číslo v českém formátu (tedy s desetinnou čárkou, místo tečky).
První příkaz z příkladu nastavuje jako oddělovač desetin tečku a jako oddělovač tisíců čárku. Je to podbné, jako nastavování NLS_DATE_LANGUAGE při konverzi data. Tentokrát jsem ale použil nastavení pro celé sezení, nejen pro volání konverzní funkce.
Session altered.
oracle> SELECT matika.soucet (3,-1),matika.soucet ('3.4',-1),matika.soucet(-1,3.4) FROM dual;
MATIKA.SOUCET(3,-1) MATIKA.SOUCET('3.4',-1) MATIKA.SOUCET(-1,3.4)
------------------- ----------------------- ---------------------
2 2.4 2
No vida, toto už vyšlo. Ale neradujte se předčasně.
V tomto posledním příkladu si Oracle zase neporadil. Člověk by čekal, že překonvertuje '-1' na integer (jako to udělal v třetím volání v předchozím příkladě s floatem), ale tentokrát se mu to nějak nelíbí.
Rozdíly mezi Postgresem a Oraclem jsou na první pohled malé, ale někdy mohou být velmi zapeklité. V této kapitole jsem vám ukázal něco málo z možností programování. Pokud vás toto téma zaujalo (a já doufám že ano), nastudujte si zbytek z dokumentace, je toho ještě hodně zajímavého, co můžete objevit :-).