PostgreSQL v roce 2009 (PostgreSQL 8.4)
PostgreSQL v roce 2009
Po dokončení verze 8.3 jsem byl přesvědčen, že 8.3 je verze, která se hned tak nepřekoná. Mýlil jsem se. 8.3 prošlápla cestu ještě lepší, funkcemi nabité, verzi 8.4. 8.3 se dokončovala poměrně bolestivě. V této verzi došlo k přepracování docela zásadních funkcí jádra databáze. To mělo celou řadu dopadů. Jednak se vytvořil prostor pro nové funkce (např. refaktoringem planneru), jednak se jasně ukázalo, že stávající vývojový proces (resp. jakési zázemí) přestává být funkční, a že musí přijít změna. Změna přišla a to v podobě tzv. commitfestů. Roční cyklus je rozdělen do čtyř tříměsíčních etap. A každá etapa se skládá z jednoho měsíce určeného k opravám chyb, vlastnímu vývoji a dvouměsíčnímu commitfestu, během kterého se mají vývojáři rozhodnout zda a které přihlášené patche integrovat do PostgreSQL. Evidence patchů je, možná trochu překvapivě, ve wiki (např. http://wiki.postgresql.org/wiki/CommitFest_2008-11). Projekt zatím zůstává u CVS, částečně, pro složitější patche, se používá Git. Docela to funguje. Opravdu se toho stihlo hodně, a prakticky ustaly diskuze o změně vývoje. To může signalizovat, že jdeme správnou cestou.
Většina té nejhorší a nejnevděčnější práce se odbyla v 8.3 (většiny změn (kromě vyššího výkonu) si uživatel nevšimne). V 8.4 je to přesně naopak. Řada funkcí byla dotažena, další, po kterých uživatelé volali, se dostaly do jádra.
Datové typy
Nově můžeme používat typ citex - case insensitive text. Lepším řešením by byla podpora COLLATIONS. Když nic jiného, tak alespoň toto:
postgres=# \i /usr/local/pgsql/share/contrib/citext.sql postgres=# create table testci(email citext); CREATE TABLE Time: 9,377 ms postgres=# insert into testci values('Pavel.Stehule@Gmail.CZ'); INSERT 0 1 postgres=# select * from testci where email like '%gmail.cz'; email ------------------------ Pavel.Stehule@Gmail.CZ (1 row) postgres=# select * from testci where email = 'pavel.stehule@gmail.cz'; email ------------------------ Pavel.Stehule@Gmail.CZ (1 row)
Pole
Nové funkce
V 8.4 se objeví několik praktických funkcí pro pole. Předně je to agregační funkce array_agg. Ve starších verzích jsme měli k dispozici funkci array_accum, které sice nebyla integrována, nicméně způsob její registrace bylo možné najít v dokumentaci. Tato funkce je ovšem dost pomalá pro větší pole (nad 1000 prvků). Tento nedostatek funkce array_agg řeší.
Další funkcí je funkce unnest. Ta převede jednorozměrné pole na tabulku:
postgres=# select * from unnest(array[1,2,3]); unnest -------- 1 2 3
Pro více rozměrná pole podobná funkce neexistuje. Nicméně k dispozici je funkce generate_subscripts, která zjednoduší iteraci nad n-rozměrným polem. Pole se hodí pro řadu úloh - např pro výběr mediánu. Tradiční SQL nijak zvlášť nepomůže. Pomocí vestavěné podpory polí a SQL funkcí můžeme tuto a podobné úlohy vyřešit efektivně a rychle:
postgres=# create function sort(anyarray) returns anyarray as $$ select array(select * from unnest($1) order by 1) $$ language sql immutable strict; CREATE FUNCTION postgres=# create or replace function median_sorted(anyarray) returns float as $$ select ($1[round(array_upper($1,1)/2.0)] + $1[array_upper($1,1) - round(array_upper($1,1)/2.0) + 1]) / 2.0::float; $$ language sql immutable strict; CREATE FUNCTION postgres=# create or replace function median(anyarray) returns float as $$ select (a[round(array_upper(a,1)/2.0)] + a[array_upper(a,1) - round(array_upper(a,1)/2.0) + 1]) / 2.0::float from sort($1) a; $$ language sql immutable strict; CREATE FUNCTION postgres=# create table test_m(a integer); CREATE TABLE postgres=# insert into test_m select (random()*1000)::int from generate_series(1,10000); INSERT 0 10000 postgres=# \timing Timing is on. postgres=# select median_sorted(array(select a from test_m order by 1)); median_sorted --------------- 502 (1 row) Time: 26,003 ms postgres=# select median(array(select a from test_m)); median -------- 502 (1 row) Time: 28,809 ms
Funkce generate_subscripts generuje posloupnost indexů zadaného pole v zadané dimenzi. Definice funkce unnest se pak zjednoduší na následující kód:
create function unnest(anyarray) returns setof anyelement as $$ select $1[i] from generate_subscripts($1,1) g(i); $$ language sql immutable strict;
Funkce unnest
K čemu se může hodit funkce unnest? Např. k inverzi agregovaných hodnot (tam, kde to je možné). Představte si, že máte tabulku obsahující vždy seznam čárkou oddělených hodnot a klíč (občas se s podobným formátem setkáte při importu):
postgres=# select * from aggregates ; v | k -----------+--- a,b,c,d,e | 1 f,g,h | 2 (2 rows)
Seznam hodnot bych chtěl převést opět na řádky. Buďto si můžu napsat uloženou proceduru, nebo využiji jednu specifickou vlastnost PostgreSQL, a to možnost umístit SRF funkci do seznamu zobrazených sloupců - na této pozici může být parametrem SRF funkce proměnná (naopak ve FROM parametry SRF funkce musí být konstanty).
postgres=# select unnest(string_to_array(v,',')), k from aggregates ; unnest | k --------+--- a | 1 b | 1 c | 1 d | 1 e | 1 f | 2 g | 2 h | 2 (8 rows)
Tuto tabulku můžeme pak klasicky zpracovávat - např. určit rozsah pro jednotlivé klíče:
postgres=# select min(v) || '..' || max(v) as rozsah, k from (select unnest(string_to_array(v,',')) v, k from aggregates ) s group by k order by k; rozsah | k --------+--- a..e | 1 f..h | 2 (2 rows)
případně vygenerovat zpět původní tabulku:
postgres=# select array_to_string(array_agg(v),','), k from (select unnest(string_to_array(v,',')) v, k from aggregates ) s group by k order by k; array_to_string | k -----------------+--- a,b,c,d,e | 1 f,g,h | 2 (2 rows)
Perfektně se funkce unnest doplňuje s variadickými funkcemi (viz níže), kde jsou parametry funkci předány jako pole hodnot:
postgres=# CREATE FUNCTION avgc(VARIADIC double precision[]) RETURNS double precision AS $$ SELECT avg(v) FROM unnest($1) v $$ LANGUAGE sql; CREATE FUNCTION postgres=# select avgc(1,2,3,null); avgc ------ 2 (1 row) postgres=# select avgc(1,2,3,4); avgc ------ 2.5 (1 row)
Funkce array_fill
Pole v PL/pgSQL musí být před prvním použitím inicializována, což vzhledem k určitým specifikům PL/pgSQL je docela pomalá operace (při větších polích). Nyní mohou programátoři použít funkci array_fill, která vrací pole specifikované inicializační hodnotou a polem dimenzí:
postgres=# select array_fill(0,array[2,2,2]); array_fill ------------------------------- {{{0,0},{0,0}},{{0,0},{0,0}}} (1 row) postgres=# select array_fill(''::varchar,array[2,2]); array_fill ------------------- {{"",""},{"",""}} (1 row)
Fulltext
Podpora statistik
V minulé verzi došlo k integraci fulltextu do jádra systému. Integrace ovšem nebyla úplná - chyběla podpora statistik. Bez statistik optimalizátor střílí naslepo, a mohlo se stát (a také se stávalo), že pro složitější dotazy obsahující fulltextové vyhledávání nebyl nalezen optimální prováděcí plán. V 8.4 je toto riziko odstraněno - statistky se generují i pro fulltext:
postgres=# explain analyze select * from film where fulltext @@ to_tsquery('dog'); QUERY PLAN --------------------------------------------------------------------------- Bitmap Heap Scan on film (cost=5.02..62.42 rows=99 width=390) (actual time=0.509..1.854 rows=99 loops=1) Recheck Cond: (fulltext @@ to_tsquery('dog'::text)) -> Bitmap Index Scan on film_fulltext_idx (cost=0.00..5.00 rows=99 width=0) (actual time=0.456..0.456 rows=99 loops=1) Index Cond: (fulltext @@ to_tsquery('dog'::text)) Total runtime: 2.230 ms (5 rows)
Podpora vyhledání prefixu
Při implementaci nejrůznějších našeptávačů jistě přijde vhod fulltextové vyhledání prefixu:
-- vyžaduje GIN index postgres=# select * from codebooks.psc_obce where to_tsvector('simple', cast_obce) @@ to_tsquery('simple', 'Bene:*'); obec | cast_obce | psc | nazev_posty | lau1 -----------------------+---------------------------+-------+-----------------------+------- Benecko | Benecko (část) | 51237 | Benecko | CZ0514 Benecko | Benecko (část) | 51401 | Jilemnice | CZ0514 Bušanovice | Beneda | 38422 | Vlachovo Březí | CZ0315 Broumov | Benešov | 55001 | Broumov 1 | CZ0523 Benešov | Benešov | 25601 | Benešov u Prahy | CZ0201 Benešov | Benešov | 67953 | Benešov u Boskovic | CZ0641
Uložené procedury
EXECUTE USING
Doposud bylo používání dynamického SQL limitováno možným rizikem SQL injektáže. Kromě toho, také, docela nečitelným výsledným kódem. Tyto problémy z části odstraňuje klauzule USING v příkazu PL/pgSQL EXECUTE. Díky ní totiž můžeme parametrizovat SQL příkaz, podobně jako v PREPARED STATEMENTS.
nezabezpečené dynamické SQL:
EXECUTE 'SELECT * FROM ' || tabname || ' WHERE value = \'' || value || '\'';
zabezpečené dynamické SQL:
EXECUTE 'SELECT * FROM ' || tabname::regclass || ' WHERE value = $1' USING value;
Díky klauzuli USING bude použití dynamického SQL o dost bezpečnější a bude s ním méně práce. I tak ale platí, že DSQL by se mělo používat, pokud možno, co nejméně a v opravdu nutných případech (např. z důvodu nutnosti generování prováděcího plánu se znalostí parametrů).
Příkaz CASE
Z prostředí SQL/PSM (standardizovaný jazyk pro uložené procedury dle ANSI - setkat se s ním můžete v DB2 nebo MySQL) byla převzata syntaxe příkazu CASE.
/* simple case */ CASE x WHEN 1, 2 THEN msg := 'one or two'; ELSE msg := 'other value than one or two'; END CASE; /* search case */ CASE WHEN x BETWEEN 0 AND 10 THEN msg := 'value is between zero and ten'; WHEN x BETWEEN 11 AND 20 THEN msg := 'value is between eleven and twenty'; END CASE;
Vlastní výjimky
PostgreSQL umožňuje předat ve výjimce relativně dost informací. PL/pgSQL příkaz RAISE byl příliš jednoduchý. Výsledkem výjimky v PL/pgSQL byl pouze jeden řetězec (nebylo možné zadat kód, hint, detail). Toto omezení je minulostí. Syntaxe příkazu RAISE byla rozšířena tak, aby umožnila specifikovat všechny dostupné parametry výjimky:
RAISE division_by_zero; RAISE SQLSTATE '22012'; RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user id'; RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation'; RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
Vlastní vyjímky lze samozřejmě zachytit - v minulých verzích bylo možné zachytit pouze systémové výjimky:
BEGIN ... EXCEPTION WHEN SQLSTATE '22012' THEN ...
Variadické funkce
Funkce, které pracují s variabilním počtem parametrů označujeme jako variadické. Mezi variadické funkce např. patří funkce least, greatest, coalesce. Implementace těchto funkcí není nijak zvlášť komplikovaná, nicméně není triviální a trochu vypadává z kontextu C funkcí (vynucuje si úpravy parseru). Na uživatelské úrovni nezbývalo než přetěžovat funkce, což vedlo k zbytečnému duplikování kódu. Řešením je zavedení tzv. variadických funkcí, resp. modifikátoru VARIADIC v seznamu parametrů. Ten pozměňuje chování parseru. Všechny reálné parametry, které jsou pozičně na pozici a za pozicí variadického parametru jsou sloučeny do pole, které je pak předáno funkci.
CREATE FUNCTION myleast(VARIADIC a numeric[]) RETURNS NUMERIC AS $$ SELECT min($1[i]) FROM generate_subscripts($1,1) g(i) $$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION Time: 5,274 ms postgres=# SELECT myleast(1,2,3,4); myleast --------- 1 (1 row)
Variadické funkci lze předhodit i pole (tj. obejít parser) pomocí modifikátoru hodnoty VARIADIC:
postgres=# SELECT myleast(VARIADIC ARRAY[1,3,4,-5,6,8]); myleast --------- -5
Tabulkové funkce
Funkce, které vrací tabulku, jsou podle ANSI SQL tzv. tabulkové funkce. Totéž (s mírně odlišnou syntaxí) se v PostgreSQL označuje jako SRF funkce (Set Returned Functions). Do 8.4 byla zahrnuta i podpora ANSI SQL syntaxe, která je podle mého názoru přehlednější a názornější:
--ANSI SQL create or replace function tab(i int) returns table(a int, b int) as $$ select i, i+1 from generate_series(1, $1) g(i); $$ language sql immutable strict; postgres=# select * from tab(2); a | b ---+--- 1 | 2 2 | 3 (2 rows) -- PostgreSQL syntaxe create or replace function tab(in int, out a int, out b int) returns setof record as $$ select i, i+1 from generate_series(1, $1) g(i); $$ language sql immutable strict;
Defaultní hodnoty parametrů funkce
Možnost specifikovat defaultní hodnoty parametrů funkce patří zvyšuje programátorský komfort a snižuje duplicitu kódu. Implementace zatím odpovídá zhruba implementaci ve Firebirdu, tj. chybějící parametry se zprava doplňují defaultní hodnoty.
postgres=# create or replace function instr(varchar, varchar, int = 1) returns int as $$ select position($2 in substring($1 from $3)); $$ language sql immutable strict; CREATE FUNCTION Time: 2,738 ms postgres=# select instr('abcabc','a'); instr ------- 1 (1 row) Time: 1,907 ms postgres=# select instr('abcabc','a',2); instr ------- 3 (1 row) Time: 1,381 ms
RETURN QUERY EXECUTE
Že se RETURN QUERY docela ujalo, mne příjemně překvapilo. Jednak jsem vůbec nečekal, že by podobné rozšíření mohlo projít do core, jednak vlastní implementace byla docela jednoduchá - v podstatě pár nových řádek v parseru, jinak se použil stávající kód. To je to, co mne na hackingu nejvíc baví. Přidá se pár řádků, pár řádků kódu se upraví a nová funkce je na světě.
V 8.4 RETURN QUERY podporuje také dynamické SQL - tj. zčásti odpadá vzor:
FOR r IN EXECUTE '....' LOOP RETURN NEXT r; END LOOP
který lze nahradit rychlejším a čitelnějším kódem:
RETURN QUERY EXECUTE '...';
Uživatelský interface
Výpis velikostí v seznamu db. objektů
Zjistit velikost tabulky nebo databáze již nebude problém.
postgres=# \dt+ codebooks.* List of relations Schema | Name | Type | Owner | Size | Description -----------+----------+-------+-------+------------+------------- codebooks | lau1 | table | pavel | 8192 bytes | codebooks | lau2 | table | pavel | 448 kB | codebooks | nuts0 | table | pavel | 8192 bytes | codebooks | nuts1 | table | pavel | 8192 bytes | codebooks | nuts2 | table | pavel | 8192 bytes | codebooks | nuts3 | table | pavel | 8192 bytes | codebooks | psc_obce | table | pavel | 1352 kB | (7 rows)
Lámání dlouhých textů ve výpisu
postgres=# \pset format wrapped Output format is wrapped. postgres=# select 'Příliš žlutý kůň se napil žluté vody', 'Příliš žlutý kůň se napil žluté vody'; ?column? | ?column? -------------------------+------------------------- Příliš žlutý kůň se nap | Příliš žlutý kůň se nap il žluté vody ; il žluté vody (1 row)
Ještě by to chtělo zalamovat po celých slovech - snad příště.
Spuštění externího editoru
Jistě se mnou souhlasíte, že konzole psql se nehodí pro editaci funkcí. Pokud máte nastavenou sys. proměnnou EDITOR můžete příkazem \ef názevfce editovat kód funkce v externím editoru.
[pavel@localhost ~]$ export EDITOR=nano [pavel@localhost ~]$ psql postgres psql (8.4devel) Type "help" for help. postgres=# \ef foo postgres-# ; CREATE FUNCTION Time: 3,605 ms
obr.
Národní prostředí
Lokalizované výstupy funkce to_char
Funkci to_char lze nyní přinutit k generování skutečně lokalizovaných datumů (tj. názvů dnů a měsíců):
postgres=# select to_char(current_date, 'tmDay, DD. tmMonth'); to_char ------------------- Středa, 11. Červen (1 row)
Separátní locales pro každou databázi
Chybějící podpora COLLATIONS značně diskvalifikuje použítí PostgreSQL jako db pro vícejazyčné aplikace. Prvním krokem v implementaci COLLATIONS je možnost nastavit locales pro každou databázi zvlášť. Drobnou nevýhodou je, že při vytváření databáze s jiným nastavením COLLATIONS je však potřeba použít za vzor databázi template0. Pokud použijete jinou šablonu, je třeba provést reindexaci nové databáze.
[pavel@localhost pgsql]$ /usr/local/pgsql/bin/psql -l List of databases Name | Owner | Encoding | Collation | Ctype | Access Privileges -----------+----------+----------+-----------------+-----------------+------------------------------------- postgres | postgres | UTF8 | cs_CZ.UTF-8 | cs_CZ.UTF-8 | template0 | postgres | UTF8 | cs_CZ.UTF-8 | cs_CZ.UTF-8 | {=c/postgres,postgres=CTc/postgres} template1 | postgres | UTF8 | cs_CZ.UTF-8 | cs_CZ.UTF-8 | {=c/postgres,postgres=CTc/postgres} testdb | pavel | LATIN2 | cs_CZ.iso8859-2 | cs_CZ.iso8859-2 | (4 rows) testdb=# set client_encoding to utf8; SET Time: 1,133 ms testdb=# select upper('žššžšž'); upper -------- ŽŠŠŽŠŽ (1 row) testdb=# select octet_length('žýř'); octet_length -------------- 3 (1 row) pavel@localhost pgsql]$ psql postgres psql (8.4devel) Type "help" for help. postgres=# select octet_length('žýř'); octet_length -------------- 6 (1 row)
Podpora COLLATIONS vzniká u nás v ČR - díky práci Radka Strnada a do core se dostala také díky nezlomnému úsilí Zdeňka Kotaly.
SQL
Analytické funkce
Během vánočních svátků, vlastně na Silvestra, byl commitnut patch Hitoshi Harady obsahující podporu analytických (window nebo OLAP) funkcí. Commit do jádra je takový vrchol snah každého vývojáře. Je to výsledek docela poctivého několika měsíčního kódování a pak také obvykle několika měsíčního lobingu, diskuzí nad kódem, desítek nejrůznějších úprav - prostě hromada práce. Na jaře byl Hitochi mírně rozčarován, že se jeho patch do jádra nedostal - bylo na něm ještě hodně práce, o čem se osobně přesvědčil - ale dotáhl to až do konce, budiž mu věčná sláva :). Když už do toho jednou člověk spadne, tak kódování je docela návyková záležitost. Už kolik let si říkám, že tohle byl můj poslední patch.
Analytické funkce nejsou novinkou - prvně se objevily již v extenzi ANSI SQL 99 (část OLAP). Na normě spolupracoval Oracle s IBM, takže byly velice brzo implementovány, a to v Oracle 8i (2001) a v DB2 7. V ostatních RDBMS se objevily o dost později (např. MS SQL server 2008), a letos je PostgreSQL první vlašťovkou v O.S. světě. Z pohledu rigidních relačních teoretiků jsou analytické funkce dost kontroverzní záležitostí - jsou mimo rámec relační algebry a také překračují prvotní architekturu, kdy se SQL server neměl starat o zobrazení dat. Návrh ještě pamatuje časy, kdy nejapnosti typu ORM byly fantazií pouze několika málo utopistů (kdy se ještě nedalo plýtvat výkonem). Na druhou stranu, díky analytickým funkcím můžeme zapomenout self join alchymii a ušetříme si pěknou řádku kódu. Takže analytické funkce byly vzaty na milost (ještě před rokem se Tom Lane vyjádřil zhruba v tom smyslu, že větší pitomost než SQL/OLAP neviděl (a v něčem měl možná pravdu) a že nečeká, že by se někdy tyto funkce do PostgreSQL dostaly. Člověk míní, a ... - dost velký podíl na tomto commitu má právě Tom Lane (ostatně jako na většině kódu v PostreSQL). V 8.4 ještě není podpora analytických funkcí kompletní, nicméně to zásadní tam je:
postgres=# SELECT * FROM foo; a | b ----+--- 1 | 1 1 | 1 2 | 1 4 | 1 2 | 1 4 | 1 5 | 1 11 | 3 12 | 3 22 | 3 16 | 3 16 | 3 16 | 3 (13 rows) postgres=# SELECT a, b, sum(a) OVER wo, postgres-# rank() OVER wo, dense_rank() OVER wo, postgres-# (percent_rank() OVER wo)::numeric(3,2), (cume_dist() OVER wo)::numeric(3,2), postgres-# count(*) OVER wo, sum(a) OVER (PARTITION BY b ORDER BY a ROWS UNBOUNDED PRECEDING) AS cum_sum, postgres-# lag(a,-1) OVER wo, lag(a,1) OVER wo, postgres-# avgc(lag(a,-1) OVER wo, a, lag(a,1) OVER wo)::numeric(4,2) AS run_avg, postgres-# first_value(a) OVER wo, last_value(a) OVER wo postgres-# FROM foo postgres-# WINDOW wo AS (PARTITION BY b postgres(# ORDER BY a postgres(# ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING); a | b | sum | rank | dense_rank | percent_rank | cume_dist | count | cum_sum | lag | lag | run_avg | first_value | last_value ----+---+-----+------+------------+--------------+-----------+-------+---------+-----+-----+---------+-------------+------------ 1 | 1 | 19 | 1 | 1 | 0.00 | 0.29 | 7 | 1 | 1 | | 1.00 | 1 | 5 1 | 1 | 19 | 1 | 1 | 0.00 | 0.29 | 7 | 2 | 2 | 1 | 1.33 | 1 | 5 2 | 1 | 19 | 3 | 2 | 0.33 | 0.57 | 7 | 4 | 2 | 1 | 1.67 | 1 | 5 2 | 1 | 19 | 3 | 2 | 0.33 | 0.57 | 7 | 6 | 4 | 2 | 2.67 | 1 | 5 4 | 1 | 19 | 5 | 3 | 0.67 | 0.86 | 7 | 10 | 4 | 2 | 3.33 | 1 | 5 4 | 1 | 19 | 5 | 3 | 0.67 | 0.86 | 7 | 14 | 5 | 4 | 4.33 | 1 | 5 5 | 1 | 19 | 7 | 4 | 1.00 | 1.00 | 7 | 19 | | 4 | 4.50 | 1 | 5 11 | 3 | 93 | 1 | 1 | 0.00 | 0.17 | 6 | 11 | 12 | | 11.50 | 11 | 22 12 | 3 | 93 | 2 | 2 | 0.20 | 0.33 | 6 | 23 | 16 | 11 | 13.00 | 11 | 22 16 | 3 | 93 | 3 | 3 | 0.40 | 0.83 | 6 | 39 | 16 | 12 | 14.67 | 11 | 22 16 | 3 | 93 | 3 | 3 | 0.40 | 0.83 | 6 | 55 | 16 | 16 | 16.00 | 11 | 22 16 | 3 | 93 | 3 | 3 | 0.40 | 0.83 | 6 | 71 | 22 | 16 | 18.00 | 11 | 22 22 | 3 | 93 | 6 | 4 | 1.00 | 1.00 | 6 | 93 | | 16 | 19.00 | 11 | 22 (13 rows)
Pozor - ORDER BY v analytických funkcích má trochu jiný význam než klauzule ORDER BY v příkazu SELECT.
Parametrické pohledy
Za tímto názvem se skrývají obyčejné SRF funkce, které byly i v předchozích verzích. V 8.4 se kód IMMUTABLE SQL funkce o jednom SQL příkazu zařadí přímo do SQL příkazu. Dochází k subtituci (podobně jako u skalárních SQL funkcí). Díky tomu planner vidí do funkce a dokáže predikovat výsledek funkce. Ve starších verzích byly SQL SRF funkce pro planner black box - a docházelo ke generování neoptimálních prováděcích plánů. Z toho důvodu se nedoporučovalo zapouzdřovat SELECTy do funkcí. Další výhodou může být vykonávání po řádcích (ty typické SRF funkce plní jakousi paměťovou tabulku a jako výsledek se předává odkaz na tuto tabulku - to je výhodné u menších výsledných množin a nevýhodné u těch velkých - kvůli nárokům na operační paměť):
postgres=# create function full_foo() returns setof foo as $$ select * from foo $$ language sql immutable; CREATE FUNCTION postgres=# create function full_foo_pl() returns setof foo as $$ begin return query select * from foo; return; end; $$ language plpgsql immutable; CREATE FUNCTION postgres=# explain select * from foo; QUERY PLAN --------------------------------------------------- Seq Scan on foo (cost=0.00..1.06 rows=6 width=4) (1 row) postgres=# explain select * from full_foo(); QUERY PLAN --------------------------------------------------- Seq Scan on foo (cost=0.00..1.06 rows=6 width=4) (1 row) postgres=# explain select * from full_foo_pl(); QUERY PLAN --------------------------------------------------------------------- Function Scan on full_foo_pl (cost=0.00..260.00 rows=1000 width=4) (1 row)
Je evidentní, že při zapouzdření SELECTu PL/pgSQL funkcí došlo k rozhození planneru. Nyní mají SQL SRF funkce skutečně blíže pohledům než funkcím - nulová režie, nulové negativní ovlivnění planneru.
Rekurzivní dotazy (Common Table Expression - CTE)
Konečně tu jsou dlouho slibované rekurzivní dotazy. Implementace v PostgreSQL vychází z ANSI SQL (klauzule WITH). Kromě ANSI SQL syntaxe existuje ještě starší, jednoduší a omezenější zápis CONNECT BY (Oracle), jehož podpora by se řadě uživatelů určitě líbila. CONNECT BY se ovšem do core nedostal. Nesešlo se dostatek argumentů pro implementaci duplicitní funkce - budiž nám alespoň útěchou, že WITH je standardní, a dost mocnější syntaxe.
PostreSQL zatím nepodporuje tzv. GROUPING SETS (Prototyp už existuje, ale sešly se tři funkce, které zásadně modifikují executor (CTE, Analytic queries a GROUPING SETS) a na GS už nezbylo dostatečně velké časové okno pro implementaci. Bez GS lze jen poměrně neefektivně kombinovat výpis agregovaných hodnot s celkovým součtem - musíme použít UNION ALL, což znamená minimálně dvojí sekvenční čtení tabulky:
postgres=# select * from prodej; nazev | kategorie | cena -----------------+-----------------+------- jogurt | mléčné produkty | 20.00 chléb - šumava | pečivo | 10.00 plnotučné mléko | mléčné produkty | 15.00 rohlíky | pečivo | 12.00 (4 rows)
S nerekurzivním CTE můžeme jedno sekvenční čtení eliminovat:
postgres=# with prehled as (select kategorie, sum(cena) cena from prodej group by kategorie ) select * from prehled union all select 'Celkem', sum(cena) from prehled; kategorie | cena -----------------+------- pečivo | 22.00 mléčné produkty | 35.00 Celkem | 57.00 (3 rows)
Uvnitř klauzule WITH plním relaci "prehled" (je to podobné použití dočasné tabulky - ovšem odpadá režie na aktualizaci hodnot v systémových tabulkách, která je spojená se vznikem a zánikem dočasné tabulky). Z relace prehled pak čtu přímo a prostřednictvím union k výsledku přidávám agregovanou hodnotou nad relací prehled.
Po CTE voláme hlavně kvůli podpoře rekurze. První ukázka obsahuje vizualizovaný výpis rekurzivní organizační struktury:
Vstupní data:
postgres=# select * from org_schema; jmeno | nadrizeny --------+----------- Libor | NULL Vráťa | Libor Petra | Libor Martin | Vráťa Pavel | Martin Marek | Martin Robin | Petra (7 rows)
Výpis:
postgres=# 01 with recursive rq as 02 (select 0 as level, jmeno as path, * 03 from org_schema where nadrizeny is null 04 union all 05 select level+1 as lev, 06 path || e'\\' || o.jmeno as path, o.* 07 from rq, org_schema o 08 where rq.jmeno = o.nadrizeny 09 ) select *, repeat(' ', level) || jmeno 10 from rq order by path; lev | path | jmeno | nadrizeny | ?column? -----+---------------------------+--------+-----------+------------- 0 | Libor | Libor | NULL | Libor 1 | Libor\Petra | Petra | Libor | Petra 2 | Libor\Petra\Robin | Robin | Petra | Robin 1 | Libor\Vráťa | Vráťa | Libor | Vráťa 2 | Libor\Vráťa\Martin | Martin | Vráťa | Martin 3 | Libor\Vráťa\Martin\Marek | Marek | Martin | Marek 3 | Libor\Vráťa\Martin\Pavel | Pavel | Martin | Pavel (7 rows)
Vnitřní dotaz v CTE (řádky 2..8) obsahuje dva dotazy - oba plní relaci rq. Separátorem je klauzule union all. První dotaz (řádky 2,3) vyhledá kořen. Druhý dotaz (řádky 5..8) se opakuje dokud vrací alespoň jeden řádek. Výsledek tohoto dotazu se přidá na zásobník a použije se jako obsah relace rq pro další iteraci. Všimněte si, že dochází ke spojení relace rq a tabulky org_schema (řádky 7,8). Kromě hodnot z tabulky org_schema si vypočítávám hloubku rekurze (sl. level) - pro kořen nastavuji 0, pak vždy zvyšuji o jedničku pro všechny potomky (rq.level + 1).
Vnější dotaz CTE (řádky 9,10) transformuje obsah relace rq (která v ten okamžik obsahuje sjednocení obsahu rq všech iterací - resp. vše, co se naakumulovalo na zásobníku) na výsledný dotaz. Na řádku 9 se na základě hodnoty v sloupci level určí odsazení (tj. dochází k vizualizaci hiearchie). Jelikož se používá metoda přibližuje metodě prohledávání do šířky (nikoliv do hloubky) je nutné výstup seřadit podle sloupce path.
Při maximálním rozlišení a velikosti okna konzole spusťe následující příkaz:
WITH RECURSIVE Z(IX, IY, CX, CY, X, Y, I) AS ( SELECT IX, IY, X::float, Y::float, X::float, Y::float, 0 FROM (SELECT -2.2 + 0.031 * i, i FROM generate_series(0,101) AS i) AS xgen(x,ix), (SELECT -1.5 + 0.031 * i, i FROM generate_series(0,101) AS i) AS ygen(y,iy) UNION ALL SELECT IX, IY, CX, CY, X * X - Y * Y + CX AS X, Y * X * 2 + CY, I + 1 FROM Z WHERE X * X + Y * Y < 16::float AND I < 100 ) SELECT array_to_string(array_agg(SUBSTRING(' .,,,-----++++%%%%@@@@#### ', LEAST(GREATEST(I,1),27), 1)),'') FROM ( SELECT IX, IY, MAX(I) AS I FROM Z GROUP BY IY, IX ORDER BY IY, IX ) AS ZT GROUP BY IY ORDER BY IY;
Schválně jestli poznáte výsledek?
ANSI SQL kompatibilní omezení výpisu
Dost dlouho standard nenabízel možnost, jak zobrazit pouze určitou podmnožinu zobrazení výsledku dotazu. Což vedlo dodavatele databází k vlastním (bohužel nekompatibilním) rozšířením. Takže u Oracle se používá ROWNUM, v MSSQL TOP, PostgreSQL a MySQL Limit (vzájemně nekompatibilně). Konečně si normovači všimly díry a zavedli do standardu limit a offset:
postgres=# select * from generate_series(1,100) offset 10 rows fetch next 5 rows only; generate_series ----------------- 11 12 13 14 15
Tato syntaxe se dost blíží tomu, co bylo a je v PostgreSQL, ale jako na potvoru, pořadí klauzulí offset a limit je přesně naopak. Pozn. Další standardní způsob omezení výpisu je použít analytické funkce (např. row_number()).
Neviditelné funkce
Visibility maps
Tzv. líné VACUUM dostalo k dispozici informaci o tom, které datové stránky tabulky byly změněny. Tím se zrychlí VACUUM na velkých a málo měněných tabulkách - VACUUM je mnohem cílenější.
Free space maps
V případě vkládaní či aktualizaci záznamů v tabulce, je třeba dohledat volné místo, kam nový záznam vložit. Do verze 8.3 informace byly ukládány ve statickém poli. V případě velkých tabulek, pak nebylo možné držet informaci o volném místě pro celou tabulku, a zbytečně se pak tabulka nafukovala. Ve verzi 8.4 byla celá správa volného místa přepracována a je nyní mnohem efektivnější. Díky tomu zmizely i dvě konfigurační položky z postgresql.conf.
Podpora semijoinu a antijoinu pro EXISTS
Tak jak se postupně planner vylepšuje, mění se i doporučení, které konstrukce preferovat. V čase před osmičkovou řadou se nedoporučovaly poddotazy s IN, pak došlo k jejich zásadnímu zrychlení. Takovému, že se začalo nedoporučovat poddotazy s EXISTS. V 8.4 došlo i na EXISTS (naopak NOT EXISTS je dotaženější než NOT IN, tj. počínaje 8.4 si dávejte pozor na NOT IN):
postgres=# explain select * from film where film_id in (select film_id from film_actor ); QUERY PLAN -------------------------------------------------------------------------------- Hash Join (cost=117.26..195.78 rows=977 width=390) Hash Cond: (film.film_id = film_actor.film_id) -> Seq Scan on film (cost=0.00..65.00 rows=1000 width=390) -> Hash (cost=105.05..105.05 rows=977 width=2) -> HashAggregate (cost=95.28..105.05 rows=977 width=2) -> Seq Scan on film_actor (cost=0.00..81.62 rows=5462 width=2) (6 rows) postgres=# explain select * from film f where exists (select * from film_actor where film_id = f.film_id); QUERY PLAN -------------------------------------------------------------------------------- Hash Join (cost=117.26..195.78 rows=977 width=390) Hash Cond: (f.film_id = film_actor.film_id) -> Seq Scan on film f (cost=0.00..65.00 rows=1000 width=390) -> Hash (cost=105.05..105.05 rows=977 width=2) -> HashAggregate (cost=95.28..105.05 rows=977 width=2) -> Seq Scan on film_actor (cost=0.00..81.62 rows=5462 width=2) (6 rows) postgres=# explain select * from film f where not exists (select * from film_actor where film_id = f.film_id); QUERY PLAN -------------------------------------------------------------------------- Hash Anti Join (cost=149.90..245.12 rows=23 width=390) Hash Cond: (f.film_id = film_actor.film_id) -> Seq Scan on film f (cost=0.00..65.00 rows=1000 width=390) -> Hash (cost=81.62..81.62 rows=5462 width=2) -> Seq Scan on film_actor (cost=0.00..81.62 rows=5462 width=2) (5 rows)
Zrychlené COPY
Došlo k optimalizaci rutin, které používá příkaz COPY. Na tabulce
create table data(a int, b float, c numeric(10,3));
s jedním miliónem řádků byl import cca o 26% rychlejší než v 8.3. Z měření by se dalo usuzovat, že rychlost importu je mnohem stabilnější - vůči 8.3 je mnohem menší rozptyl 8139 +/- 157ms proti 11093 +/- 1376 ms.
Zrychlený HASH index
Do verze 8.3 HASH index nebyl doporučovaný. Jednak nemá implementovaný WAL, takže při pádu se může poškodit, a jednak neexistoval případ, kdy by byl rychlejší než B-TREE. V rámci GSoC se implementace HASH indexu ve verzi 8.4 změnila a nyní se ukládá do indexu jen spočtená 32bitová hodnota hash funkce. Díky tomu dochází k rychlému vyhledání dlouhých VARCHAR položek. Pro hodnoty typu integer se rychlost příliš nezmění.
Paralení import dumpu
pg_restore (utilita pro import binárního dumpu) dokáže otevřít víc spojení do databáze a dump načítat, zpracovávat v souběžných procesech (příp. vláknech na MS Windows). Kupodivu to funguje. Na syntetickém příkladu importu dvou tabulek v relaci 1:N (obě tabulky byly stejně velké) došlo k dvojnásobnému zrychlení importu (při dvou procesech). Je otázkou, jak se ukáže tato funkce v praxi.
[pavel@localhost bin]$ ./dropdb test [pavel@localhost bin]$ ./createdb test [pavel@localhost bin]$ time ./pg_restore -d test ~/data.dta real 0m16.649s user 0m1.171s sys 0m0.027s [pavel@localhost bin]$ ./dropdb test [pavel@localhost bin]$ ./createdb test [pavel@localhost bin]$ time ./pg_restore -d test -m2 ~/data.dta real 0m8.782s user 0m0.811s sys 0m0.031s
Zabezpečení
Práva na sloupce
Počínaje verzí 8.4 můžeme nastavovat přístupová práva i na jednotlivé sloupce tabulky.
postgres=# create role p; CREATE ROLE postgres=# create table foob(a int, b int); CREATE TABLE postgres=# insert into foob values(1,2); INSERT 0 1 postgres=# GRANT all ( b ) on table foob to public; GRANT postgres=# set role p; SET postgres=> select * from foob; ERROR: permission denied for relation foob postgres=> select b from foob; b --- 2 (1 row)
Dohled
Provozní statistiky - pohled pg_stat_user_functions
Na otázku jaké tabulky a indexy se používají a jak intenzivně dokážeme odpovědět díky dotazům do pohledů pg_stat_*_tables a pg_stat_*_indexes. Od 8.4 je možné podobně monitorovat i funkce. Fantazii se meze nekladou - zrovna teď by se mi tato funkce hodila - bohužel migrujeme na 8.3 a nikoliv na 8.4. Během migrace z 8.1 na 8.3 se snažíme trochu vyčistit kód a přišel jsem na to, že s vysokou pravděpodobností se nám v databázi nahromadily nepoužívané (mrtvé funkce). V 8.3 si musím pomoci a skriptem do všech funkcí přidat kód, který zapíše volání funkce do připravené tabulky. V 8.4 je monitorování funkcí defaultně vypnuto.
postgres=# set track_functions to 'pl'; SET postgres=# create or replace function test(i integer) returns int as $$ begin perform pg_sleep(1); return i; end;$$ language plpgsql; CREATE FUNCTION postgres=# SELECT test(10); test ------ 10 (1 row) postgres=# select * from pg_stat_user_functions; funcid | schemaname | funcname | calls | total_time | self_time --------+------------+----------+-------+------------+----------- 24608 | public | test | 1 | 1002 | 1002 (1 row)
Pozor - zdrojem dat pro tento pohled je executor. U skalarních SQL funkce (a non volatile SRF single SQL funkcí) se uplatňuje inlining. Z pohledu executoru neexistují a tudíž se ani nemohou objevit v tomto pohledu.
RPM repozitory
Na adrese http://yum.pgsqlrpms.org/ si můžete vyzkoušet PostgreSQL repository. Vůči distribuci obsahuje navíc balíčky knihoven, které se do distribuce nedostaly (např. orafce, plpgsh, plpgpsm) a můžete si vybrat verzi PostgreSQL, kterou potřebujete, tj. 8.3 je připravena pro RHEL/CentOS 5, RHEL/CentOS 4, Fedora 7-10 a pod. A nechybí ano možnost nainstalovat si z rep. neprodukční vývojovou verzi. Nyní i v RPM je možnost mít nainstalováno víc verzí (starší balíčky se přepisovaly).
epsql
epsql (experimental psql) je čistě privátní záležitost. SQL konzole psql musí za všech okolností a všech prostředích pracovat bez chyb. Je to první aplikace, prostřednictvím ní uživatel získá první dojem o PostgreSQL. Tudíž jsou core vývojáři poměrně hodně nedůtkliví ohledně požadavků na přidávání nových funkcí. Je to hodně konzervativní aplikace - podobně jako Oraclovské sqlplus. Tudíž jsem udělal vlastní fork. Pár dní jsem si hrál a psql upravil tak, že používá UTF rámečky a ANSI escape kódy pro zvýraznění záhlaví tabulek (a kromě toho, to rozumně zalamuje (v mezerách)). epsql jsem je tzv postcardware, takže jestli se Vám epsql líbí, pošlete pohlednici na adresu uvedenou v README.
Rozšířená podpora stylů
K dispozici jsou 4 styly rámečků a 7 stylů vykreslení. Je možné nastavit escape sekvenci (barvy a zvýraznění) pro řádek se záhlavím sloupců a pro jednotlivá záhlaví.
postgres=# \pset borderlinestyle 0 Border line style is 0. postgres=# \pset border 2 Border style is 2. postgres=# \l List of databases +-----------+----------+----------+-------------+-------------+-----------------------+ | Name | Owner | Encoding | Collation | Ctype | Access privileges | +-----------+----------+----------+-------------+-------------+-----------------------+ | postgres | postgres | UTF8 | cs_CZ.UTF-8 | cs_CZ.UTF-8 | | | template0 | postgres | UTF8 | cs_CZ.UTF-8 | cs_CZ.UTF-8 | =c/postgres | | : postgres=CTc/postgres | | template1 | postgres | UTF8 | cs_CZ.UTF-8 | cs_CZ.UTF-8 | =c/postgres | | : postgres=CTc/postgres | +-----------+----------+----------+-------------+-------------+-----------------------+ (3 rows) postgres=# \pset borderlinestyle 1 Border line style is 1. postgres=# \pset border 3 Border style is 3. postgres=# \l List of databases ┌───────────┬──────────┬──────────┬─────────────┬─────────────┬───────────────────────┐ │ Name │ Owner │ Encoding │ Collation │ Ctype │ Access privileges │ ├───────────┼──────────┼──────────┼─────────────┼─────────────┼───────────────────────┤ │ postgres │ postgres │ UTF8 │ cs_CZ.UTF-8 │ cs_CZ.UTF-8 │ │ ├───────────┼──────────┼──────────┼─────────────┼─────────────┼───────────────────────┤ │ template0 │ postgres │ UTF8 │ cs_CZ.UTF-8 │ cs_CZ.UTF-8 │ =c/postgres │ │ ┊ ┊ ┊ ┊ : postgres=CTc/postgres │ ├───────────┼──────────┼──────────┼─────────────┼─────────────┼───────────────────────┤ │ template1 │ postgres │ UTF8 │ cs_CZ.UTF-8 │ cs_CZ.UTF-8 │ =c/postgres │ │ ┊ ┊ ┊ ┊ : postgres=CTc/postgres │ └───────────┴──────────┴──────────┴─────────────┴─────────────┴───────────────────────┘ (3 rows) postgres=# \pset border 2 Border style is 2. postgres=# \pset borderlinestyle 3 Border line style is 3. postgres=# \l List of databases ╔═══════════╤══════════╤══════════╤═════════════╤═════════════╤═══════════════════════╗ ║ Name │ Owner │ Encoding │ Collation │ Ctype │ Access privileges ║ ╟───────────┼──────────┼──────────┼─────────────┼─────────────┼───────────────────────╢ ║ postgres │ postgres │ UTF8 │ cs_CZ.UTF-8 │ cs_CZ.UTF-8 │ ║ ║ template0 │ postgres │ UTF8 │ cs_CZ.UTF-8 │ cs_CZ.UTF-8 │ =c/postgres ║ ║ ┊ ┊ ┊ ┊ : postgres=CTc/postgres ║ ║ template1 │ postgres │ UTF8 │ cs_CZ.UTF-8 │ cs_CZ.UTF-8 │ =c/postgres ║ ║ ┊ ┊ ┊ ┊ : postgres=CTc/postgres ║ ╚═══════════╧══════════╧══════════╧═════════════╧═════════════╧═══════════════════════╝ (3 rows)
Číslování řádků výpisu zdrojového kódu funkce
Funkce, která mi hodně chyběla je listing funkce s číslováním řádků. Proto je v epsql. Metapříkaz \lf má odlehčenou variantu - se symbolem mínus. V případě použití odlehčené varianty se nečíslují řádky výpisu.
postgres=# select test(10); ERROR: division by zero CONTEXT: PL/pgSQL function "test" line 3 at assignment postgres=# \lf test *** CREATE OR REPLACE FUNCTION public.test(a integer) *** RETURNS integer *** LANGUAGE plpgsql *** AS $function$ 1 declare b int; 2 begin 3 b := a/0; 4 return a; 5 end; *** $function$
Integrace kurzorů
V psql se docela nepohodlně přenášejí data z databáze k dalšímu zpracování. Co umí psql dobře, je zobrazení výsledku dotazu. Pro cokoliv ostatního je skoro jednodušší si napsat uloženou proceduru nebo skript v bashi. Pokusem o vyřešení omezenosti psql je integrace podpory kurzorů - metapříkazy \fetch a \fetchall. Metapříkaz fetch přečte záznam z kurzoru a jeho obsah uloží do psql proměnných. S obsahem těchto proměnných pak můžeme dále pracovat:
postgres=# BEGIN; BEGIN postgres=# DECLARE c CURSOR FOR SELECT * FROM pg_database; DECLARE CURSOR postgres=# \fetch c \echo :datname :datcollate template1 cs_CZ.UTF-8 postgres=# \fetch c \echo :datname :datcollate template0 cs_CZ.UTF-8 postgres=# \fetch c \echo :datname :datcollate postgres cs_CZ.UTF-8 postgres=# \fetch c \echo :datname :datcollate ERROR: (not available) postgres=# COMMIT; COMMIT
Metapříkaz \fetchall provede totéž co příkaz fetch a navíc pro každý záznam všechny příkazy v psql bloku - tj. na jednom řádku:
postgres=# CREATE TABLE test1(a int); CREATE TABLE test2(a int); CREATE TABLE CREATE TABLE postgres=# BEGIN; BEGIN postgres=# DECLARE c CURSOR FOR SELECT tablename FROM pg_tables WHERE tablename LIKE 'test%'; DECLARE CURSOR postgres=# \fetchall c DROP TABLE :tablename; DROP TABLE DROP TABLE postgres=# COMMIT; COMMIT postgres=#
Na co se letos nedostalo
Na podpoře GROUPING SETS mně čeká ještě dost práce. Snad do léta by to mohlo být v CVS HEADu. Naopak, podpora jmeného a kombinovaného předávání parametrů (named and mixed notation) je téměř hotová, bohužel s jinou syntaxí než známe z ADY nebo z Oraclu. Uvažuje se o lepší podpoře C++ (pro UDF - User Defined Functions). Snad se začne pracovat na podpoře COLLATION dle ANSI SQL a koncepčněji se začalo řešit další směřování partitioningu. Z celoroční práce KaiGaie Koehiho se v 8.4 dostalo pouze na základní integraci SELinuxu. Doufejme, že se během tohoto roku povede dokončit i druhou část, tj. přístupová práva k řádkům (row level security). V 8.5 by se mohly objevit některé funkce, které zjednoduší použití PostgreSQL i pro laiky - např. wizard pro configuraci postgresql.conf nebo index advisor. Začalo se pracovat na SQL/MED, a je dost možné, že se stávající statistiky doplní o statistiku čekání na uvolnění zámků. V 8.5 téměř jistě bude podpora režimu Hot StandBy.
Docela příjemné zjištění je, že do 8.4 nezanedbatelně přispěli i našinci. Jinak práce na Postgresu je stále ještě hodně. Pokud např. hledáte dobré a praktické téma na diplomovou práci a programování máte v malíků, podívejte se na naši nabídku témat diplomových prací. Prací pro velký softwareový projekt je možně se lecčemu naučit a navíc Vaše práce nemusí přijít nazmar.
Nerad bych to zakřikl, ale mám určité tušení, že 8.4, tu bude stejně dlouho a bude stejně zásadní jako byla verze 7.4. A dost možná, že další verze už bude první v devítkové řadě - intenzivně se začalo pracovat na integraci replikací a clusteringu do jádra - to se teď rýsuje jako hlavní cíl pro rok 2009. Málokdo dokáže odhadnout co bude v dalších dvou letech, a jak se projeví ekonomická krize. Začíná být patrná snaha ušetřit na licencích Oracle a migrovat na O.S. všechny nebo některé nekritické nebo pro firmy nevýdělečné systémy (tam, kde dříve výhradně používali komerční software). Uvidíme. Čeká nás zajímavý rok.
Související články
- Novinky 2006 (PostgreSQL 8.2)
- PostgreSQL 10 (2017) - drsně rozběhnutý slon
- PostgreSQL 11 (2018)
- PostgreSQL 12 (2019)
- PostgreSQL 13 (2020)
- PostgreSQL 14 (2021)
- PostgreSQL 15 (2022)
- PostgreSQL 16 (2023)
- PostgreSQL 17 (2024)
- PostgreSQL 9.0 - nový začátek
- PostgreSQL 9.1 - aneb stále vpřed
- PostgreSQL 9.2 (2012)
- PostgreSQL 9.3 (2013)
- PostgreSQL 9.4 (2014): transakční sql json databáze
- PostgreSQL 9.5 (2015) držte si klobouky, zrychlujeme
- PostgreSQL 9.6 (2016) odteď paralelně
- PostgreSQL v roce 2009 (PostgreSQL 8.4)
- Slon nezapomíná (co nás čeká v PostgreSQL 8.3)