Archiv pro štítek: SQL

Funkce a triggery v PostgreSQL

postgresql-triggerNásledující článek se bude hodit každému, kdo již má určité zkušenosti s SQL a rád by si vyzkoušel napsat vlastní funkci nebo trigger. Začneme velmi zvolna, nejprve si představíme, jak napsat jednoduchou funkci a následně se vrhneme na triggery.

Vlastní funkce v PostgreSQL

Funkce dělá přesně to, co její matematický protějšek. Pro vstupní hodnotu vrátí určitý údaj. My si nyní vytvoříme funkci, co nebude vyžadovat argument a vždy vrátí číslo jedna — to jen pro zahřátí, taková funkce samozřejmě není moc užitečná, ale alespoň se podíváme, jak vlastně vypadá její syntaxe.


CREATE FUNCTION test() RETURNS integer AS $$

SELECT 1 AS result;

$$ LANGUAGE SQL;

SELECT test(); --vypíše 1

Nejprve s pomocí CREATE FUNCTION vymezíme, že začínáme psát funkci. Poté uvedeme její název (zde test) a prázdnou závorku, protože nepřebíráme žádné argumenty. Databázový server zajímá, jaký datový typ bude naše funkce vracet, proto jej uvedeme (integer). Za klíčovým slovem AS začíná vymezení samotného těla funkce, které z obou strany obklopuje symbol dvou dolarů ($$). Závěrem uvedeme použitý jazyk (LANGUAGE SQL) a nezapomeneme na středník, podobně jako v těle funkce, ve kterém jednoduše s pomocí příkazu SELECT získáme číselnou hodnotu 1.

Pojďme zkusit něco užitečnějšího, naučíme se používat argumenty:


CREATE FUNCTION plus(integer, integer) RETURNS integer AS $$
 SELECT $1 + $2 AS result;
$$ LANGUAGE SQL;

SELECT plus(10, 5); --vypíše 15

SELECT plus(5, 'a'); --Error in query: ERROR: invalid input syntax for integer: "a"

Tato funkce umí sečíst dvě čísla. U argumentů uvádíme jen datový typ a na hodnotu se odkazujeme s pomocí dolaru a indexu (první argument je dostupný s pomocí $1).

Než se vhrneme na náš první trigger, smažeme z databáze vytvořené funkce. Udělá se to následovně:


DROP FUNCTION test();

DROP FUNCTION plus(integer, integer);

Jednoduchý trigger v PostgreSQL

Na úvod si představme, že máme tabulku human  obsahující údaje o lidech. Uchováváme jedinečné ID, jméno, příjmení a oslovení. Napíšeme si trigger, který nám bude zmíněné oslovení generovat automaticky. Nejprve vytvoříme samotnou funkci, kterou server následně automaticky zavolá. Daná funkce nebude dělat nic jiného, než že vezme jméno i příjmení a spojí oba údaje mezerou.

tabulka_human


CREATE FUNCTION concatenate() returns trigger AS $$
BEGIN
UPDATE human SET salutation = (NEW.name || ' ' || NEW.surname) WHERE id = NEW.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;

Oproti zápisu funkce je zde několik rozdílů. Trigger nikdy nemá argumenty a jako návratový typ uvádíme slovo trigger. Do těla se nám dostala část BEGIN a END; (pozor na středník). Uvnitř těla smíme používat proměnnou NEW, která odkazuje na nově vytvořený/upravený řádek. Příkazem RETURN vracíme výsledek funkce (zde je NULL, protože nic vracet nechceme). Klauzule LANGUAGE obsahuje plpgsql, protože píšeme trigger pro PostgreSQL.

Pojďme nyní naší tabulku donutit, ať trigger volá automaticky při každém vložení hodnoty:


CREATE TRIGGER human_trigger AFTER INSERT ON human
FOR EACH ROW EXECUTE PROCEDURE concatenate();

Zápis je hodně upovídaný a osobně se mi špatně pamatuje. Zase se naopak dobře čte :-). Uvádíme pojmenování našeho triggeru (human_trigger), název tabulky (human) a samotnou funkci, co bude volána (concatenate());

A nyní pozor, stačí nám spustit následující příkaz a v tabulce objevíme automaticky vygenerované oslovení:


INSERT INTO human(name, surname, salutation) VALUES ('Jan', 'Novák', '?');

Sloupec salutation nedovoluje hodnoty NULL, proto musíme nějakou uvést, i když bude následně přepsána triggerem. To je nepříjmené, pojďme si náš trigger vylepšit! Funkci nebudeme volat po zápisu řádku, ale před zápisem (a v tuto chvíli si vygenerujeme oslovení).


CREATE FUNCTION concatenate() returns trigger AS $$
BEGIN
NEW.salutation := NEW.name || ' ' || NEW.surname;
return new;
END;
$$ LANGUAGE plpgsql;

S pomocí proměnné NEW přiřadíme oslovení. Hotovu funkci poté budeme volat před provedením INSERT nebo v případě UPDATE:


CREATE TRIGGER human_trigger BEFORE INSERT OR UPDATE ON human
 FOR EACH ROW EXECUTE PROCEDURE concatenate();