Někdy je potřeba provést náročné výpočty, přesuny dat, nebo vytvořit business rules (obchodní pravidla). Tehdy sáhnete pravděpodobně po serverovém programování buďto přímo přes rozhraní, nebo po procedurálních jazycích. PostgreSQL nabízí plnou náruč procedurálních jazyků PL/pqSQL, PL/Tcl,PL/Perl,PL/Python atd.
Já pocházím ze starší generace, která vyrůstala na jazyku 4GL (jazyk 4. generace) a kdy v 90. letech minulého století vládl Oracle a PL/SQL. Takže relační datový model, serverové programování, silná databáze a PL/SQL mám v krvi. V tomto mém příspěvku Vám chci sdělit pár základních pravidel, která se snažím dodržovat v PL/pgSQL a seznámit Vás s tímto procedurálním jazykem.
Základní struktura PL/pgSQL umožňuje uložit práci ve formě funkce, nebo použít anonymní blok. Anonymní blok je série příkazů v jednom bloku, které potřebujete spustit, aniž byste uložili svoji práci do databáze formou funkce.
Než bych o tom dlouho vykládal, spusťte pgAdmina. Otevřete databázi, a v toolosech query (ctrl + e).
A zadejte tento sql script.
create table stanice
(stanice_id serial primary key,
stanice text not null unique);
create table stanice_prumer_teplota_mesic
(
stanice_id integer not null,
rok smallint not null check (rok > 2000 and rok < 2100),
mesic smallint not null check (mesic >=1 and mesic <= 12),
teplota numeric(3,1) not null,
primary key (stanice_id,rok,mesic)
);
alter table stanice_prumer_teplota_mesic
add constraint fk_stanice_prumer_teplota_mesic_stanice foreign key (stanice_id)
references stanice (stanice_id);
create table log_error
(id serial primary key,
datum timestamp not null,
zdroj text not null,
parametry text not null,
message text not null,
table_name text,
schema_name text);
CREATE OR REPLACE FUNCTION f_stanice_mesic_data_insert
(pstanice text, -- název stanice
prok integer, -- rok měření
pmesic integer, -- měsíc měření
pteplota numeric(3,1)) -- teplota
RETURNS smallint AS -- 1 vše OK - 0 chyba
$BODY$
DECLARE
lstanice_id integer;
lmessage text;
lexp_detail text;
lexp_hint text;
lexp_table text;
lexp_schema text;
BEGIN
/*
* Vlozeni dat o mesicnich prumernych teplotach
*
* tables : stanice, stanice_prumer_teplota_mesic
*
* verze : 1
*
*/
BEGIN
-- vlozeni stanice
select stanice_id into lstanice_id
from stanice
where stanice = pstanice;
IF (lstanice_id is null) THEN
INSERT INTO stanice (stanice)
values (pstanice);
lstanice_id = lastval();
END IF;
insert into stanice_prumer_teplota_mesic
( stanice_id,rok,mesic,teplota)
values
(lstanice_id,prok,pmesic,pteplota);
/*
Osetrene chybove stavy :
23000 integrity_constraint_violation
23001 restrict_violation
23502 not_null_violation
23503 foreign_key_violation
23505 unique_violation
23514 check_violation
23P01 exclusion_violation
*/
EXCEPTION
WHEN SQLSTATE '23000' OR SQLSTATE '23001' OR SQLSTATE '23502' OR
SQLSTATE '23503' OR SQLSTATE '23505' OR SQLSTATE '23503' OR
SQLSTATE '23514' OR SQLSTATE '23P01' THEN
GET STACKED DIAGNOSTICS lmessage = MESSAGE_TEXT,
lexp_detail = PG_EXCEPTION_DETAIL,
lexp_hint = PG_EXCEPTION_HINT,
lexp_table = TABLE_NAME,
lexp_schema = SCHEMA_NAME;
insert into log_error
(datum, zdroj, parametry, message, table_name,schema_name)
values
(now(),'FUNCTION f_stanice_mesic_data_insert',
'pstanice:'||coalesce(pstanice,'') ||
', prok:' ||coalesce(prok::text,'') ||
', pmesic:'||coalesce(pmesic::text,'') ||
', pteplota:'||coalesce(pteplota::text,''),
lmessage ||' -- '|| lexp_detail,
lexp_table,lexp_schema);
RETURN 0;
END;
RETURN 1;
END;
$BODY$
LANGUAGE plpgsql;
SQL script vytvoří tři tabulky „stanice“ a „stanice_prumer_teplota_mesic“ a „log_error“. Znamená to, že budeme evidovat meteostanice a k nim průměrné měsíční hodnoty v jednotlivých měsících daného roku. A chyby v zadávaní dat budou v tabulce „log_error“. U tabulek si všimněte, že jsem věnoval čas tomu, abych zabezpečil integritu jednak formou cizího klíče, not null, datovým typem a checkem. Pak jsem sestavil funkci, která na základě vstupních dat vloží data do příslušné tabulky. Funkci spustíte takto:
select * from f_stanice_mesic_data_insert('Brno, Tuřany'::text,2016,1,-1.0);
Funkce vám vrátí 1 – insert proběhl, nebo 0 – vyskytla se chyba. V případě chyby je log zapsán v tabulce „log_error“. V případě, opětovného spuštění
select * from f_stanice_mesic_data_insert('Brno, Tuřany'::text,2016,1,-1.0);
do log_error se vypíše toto chybové hlášení:
select * from log_error;
1 | 2016-11-09 18:32:35.236833 | FUNCTION f_stanice_mesic_data_insert |
pstanice:Brno, Tuřany, prok:2016, pmesic:1, pteplota:-1.0 |
duplicate key value violates unique constraint "stanice_prumer_teplota_mesic_pkey" -- Key (stanice_id, rok, mesic)=(1, 2016, 1) already exists. |
stanice_prumer_teplota_mesic | public
A nyní co jsem v definici funkci udělal a proč.
SQL skripty si pravidelně ukládám do textového souboru. Pro Query ikona s disketou.
V případě, že potřebujete ladit funkci lze to. Viz https://www.pgadmin.org/docs4/dev/debugger.html . Pouze Vám doporučuji dělat debuggování nejlépe na lokální databázi, abyste zbytečně neblokovali zdroje databáze. Vykonávaní PL/pgSQL probíhá v jedné transakci co má výhody i nevýhody. Hlavně alokaci zdrojů db. Ale lze se obejít i bez debuggeru. Ne vždy jsou podmínky na to, aby mohl být zapnutý. V tomto případě používám techniky temp tabulky, checkpointy, redukce vzorových dat a dělení kódu. Ale o tom někdy jindy.
pgAdmin velice dobře poslouží i jako kontrola nainstalovaných funkcí. Může se k tomu využít kliknutí na hlavičku funkce pravým tlačítkem a vybrat „Skripty“ a „Skript CREATE“. Nebo script vidíte na záložce „panel SQL“. Pokračování příště …
Poznámka :
Text neprošel jazykovou úpravou. Jedná se u můj blog, kde podstatné jsou technikálie. Nechám otevřenou diskusi, protože věřím, že bude mnoho námětů a připomínek. Jakýkoliv příspěvek, který se nezabývá technikáliemi vymažu. Děkuji za pochopení.
Tady si dovolím oponovat - funkce má vrátit hodnotu nebo vyhodit vyjímku - vracet 1 nebo 0 nemá v jazyku, který podporuje obsluhu vyjímek smysl, a je to zrovna hezká ukázka toho, co by se dělat nemělo. V Postgresu se neobsloužená výjimka zapíše do logu - můžete mít log v CSV formátu a pak skrz FDW file se k tomu logu dostat z SQL. Technika, kterou ukazujete vede k dost nečitelnému a zbytečně nafouklému kódu,
BEGIN, END -- není to Pascal (ale ADA) potažmo ALGOL
PGAdmin má dost mizerný editor - mnohem praktičtější je psát skripty v klasickém programátorském editoru, a pak je skrz psql naiportovat do databáze. Navíc PGAdmin svádí k direktivním změnám objektů (třeba funkcí), a pak člověk nemá ani ten soubor.
Konflikty názvů proměnných z PLpgSQL a SQL jsou už pár let ošetřeny (pokud si uživatel nevypne kontrolu, tak se při konfliktu vyhodí vyjímka). Tradičnější je používání "_" pro proměnné plpgsql a aliasy pro SQL identifikátory.
Bez debuggeru se lze obejít docela dobře - díky ladícímu výstupu - příkaz RAISE NOTICE
Napsal jste skalární funkci - tj nevrací tabulku (ani kompozit) - měl byste ji volat ve skalárním kontextu
SELECT fx() nikoliv SELECT * FROM fx()
Pavle naprostý souhlas, lepší je nechat vybublat výjimku od spoda nahoru než do nekonečna psát
if fce1() = 1 then
if fce2() = 1 then
else ...
else ...
autor pochází ze starší generace a v "90. letech minulého století" se bohužel takto tvořilo. Dnes upravovat, opravovat takovou práci je peklo.
jak se setřilo místem je to samé
cursor a is ...
cursor b is ...
cursor c is ...
člověk pak musí být nadán genialitou nebo mít dobrého cvokaře.
To nemluvím o borcích co jsou shopni napsat:
when others then null;
Děkuji za názor. Ne ze všemi Vašimi názory souhlasím.
S ADou Jste mněl pravdu. 20 let jsem žil v omylu. S RAISE NOTICE jste mi vypálil rybník.
Zde příklad konfliktu proměnných. Úplně na konci je příklad konfliktů názvů, který projde.
create table test2
(id integer primary key,
slovo text);
insert into test2 values (100,'test');
create function test2_update(slovo text)
returns void
as
$body$
begin
update test2
set slovo=slovo;
end;
$body$
LANGUAGE plpgsql;
select test2_update('jde se na to');
Výsledek:
ERROR: nejednoznačný odkaz na sloupec "slovo"
LINE 2: set slovo=slovo
^
DETAIL: Identifikátor může odkazovat na proměnnou PL/pgSQL nebo na sloupec v tabulce.
QUERY: update test2
set slovo=slovo
CONTEXT: PL/pgSQL funkce test2_update(text) řádek 3 na SQL příkaz
********** Chyba **********
ERROR: nejednoznačný odkaz na sloupec "slovo"
Stav SQL: 42702
Podrobnosti:Identifikátor může odkazovat na proměnnou PL/pgSQL nebo na sloupec v tabulce.
Kontext:PL/pgSQL funkce test2_update(text) řádek 3 na SQL příkaz
ALE !!!
create function test2_update2(slovo1 text)
returns void
as
$body$
declare
slovo1 text default 'ahoj';
begin
update test2
set slovo=slovo1;
end;
$body$
LANGUAGE plpgsql;
select test2_update2('jde se na to'); -- PROJDE
vysledek :
select * from test2;
100;"ahoj" -- Coz je zmatecne
To ale není konflikt SQL identifikátoru a PLpgSQL proměnné - jedná se o tzv zastínění proměnné - pro každý blok můžete deklarovat proměnné bez ohledu na vnější bloky a používá se proměnná z aktuálního bloku. PLpgSQL se chová správně.
Nicméně toto chování může být pro programátora z jazyků, kde tato možnost není překvapivá, a proto je lze blokovat nebo si vynutit varování:
SET plpgsql.extra_warnings TO 'shadowed_variables'; CREATE FUNCTION foo(f1 int) RETURNS int AS $$ DECLARE f1 int; BEGIN RETURN f1; END $$ LANGUAGE plpgsql; WARNING: variable "f1" shadows a previously defined variable LINE 3: f1 int; SET plpgsql.extra_errors to 'shadowed_variables'; create or replace function shadowtest(f1 int) returns boolean as $$ declare f1 int; begin return 1; end $$ language plpgsql; ERROR: variable "f1" shadows a previously defined variable LINE 3: declare f1 int; begin return 1; end $$ language plpgsql;
https://www.postgresql.org/docs/9.5/static/plpgsql-development-tips.html
Jestli kód je napsaný správným stylem nebo ne, to nechám na posouzení jiným. Každopádně oceňuju článek o tom, že se dá PostgreSQL použít i k jiným věcem než k základnímu uložení dat a že se tam dají dělat daleko složitější věci.
Dnes je takový trend, že se relační databáze použije právě jen k uložení dat a veškeré kontroly nebo business pravidla jsou naprogramovány až třeba v ORM a výš. To je zase druhý extrém, který vede k dost neudržitelnému kódu a dřív nebo později to vede k nekonzistenci dat.
SQL Developer
Přečteno 31 976×
Přečteno 19 551×
Přečteno 19 506×
Přečteno 17 275×
Přečteno 16 238×