<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="cs">
	<id>http://postgres.cz/index.php?action=history&amp;feed=atom&amp;title=CTE_a_analytick%C3%A9_funkce_v_PostgreSQL</id>
	<title>CTE a analytické funkce v PostgreSQL - Historie editací</title>
	<link rel="self" type="application/atom+xml" href="http://postgres.cz/index.php?action=history&amp;feed=atom&amp;title=CTE_a_analytick%C3%A9_funkce_v_PostgreSQL"/>
	<link rel="alternate" type="text/html" href="http://postgres.cz/index.php?title=CTE_a_analytick%C3%A9_funkce_v_PostgreSQL&amp;action=history"/>
	<updated>2026-05-30T20:26:41Z</updated>
	<subtitle>Historie editací této stránky</subtitle>
	<generator>MediaWiki 1.43.3</generator>
	<entry>
		<id>http://postgres.cz/index.php?title=CTE_a_analytick%C3%A9_funkce_v_PostgreSQL&amp;diff=455&amp;oldid=prev</id>
		<title>imported&gt;Pavel: /* Common Table Expression */</title>
		<link rel="alternate" type="text/html" href="http://postgres.cz/index.php?title=CTE_a_analytick%C3%A9_funkce_v_PostgreSQL&amp;diff=455&amp;oldid=prev"/>
		<updated>2009-05-07T07:26:23Z</updated>

		<summary type="html">&lt;p&gt;&lt;span class=&quot;autocomment&quot;&gt;Common Table Expression&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Nová stránka&lt;/b&gt;&lt;/p&gt;&lt;div&gt;[[Category:Články]]&lt;br /&gt;
V tomto článku bych rád ukázal cestu ke dvěma novým funkcím PostgreSQL - &amp;lt;i&amp;gt;CTE&amp;lt;/i&amp;gt; a &amp;lt;i&amp;gt;analytickým funkcím&amp;lt;/i&amp;gt;. Z pohledu SQL o nic nového nejde - tyto funkce jsou dobře známé z komerčních databází. Z pohledu OSS databází se jedná o významný krok k plné implementaci ANSI SQL 200x a k znatelnému rozšíření komfortu vývojářů používajících tyto databáze. &lt;br /&gt;
&lt;br /&gt;
==Common Table Expression==&lt;br /&gt;
&amp;lt;i&amp;gt;CTE&amp;lt;/i&amp;gt; nejsou jen rekurzivní dotazy, přesto si dovedu představit, že většina vývojářů použije tuto funkcionalitu právě na rekurzivní dotaz. Co jsem si mohl všimnout, tak pro řešení rekurzivních dotazů CTE soupeří se syntaxí Oraclu &amp;lt;tt&amp;gt;CONNECT BY&amp;lt;/tt&amp;gt;. &amp;lt;tt&amp;gt;CONNECT BY&amp;lt;/tt&amp;gt; je zavedená syntaxe - byť na mne působí trochu cizorodě (v SQL) a trochu jako rychlý hack. Přes své rozšíření se do standardu tato klauzule nedostala a proto také nebyla implementována v PostgreSQL (přestože několik let je dostupný patch, který CONNECT BY implementuje do PostgreSQL). &amp;lt;tt&amp;gt;CONNECT BY&amp;lt;/tt&amp;gt; jsem popisoval v článku [http://www.root.cz/clanky/stromy/ stromy]. &lt;br /&gt;
&lt;br /&gt;
Vůči &amp;lt;tt&amp;gt;CONNECT BY&amp;lt;/tt&amp;gt; je syntaxe CTE univerzálnější a nesoustřeďuje se pouze na rekurzivní dotazy. Zatímco pro CONNECT BY je základem určení sloupců identifikátoru řádku a identifikátoru předka (plus určení řádku s kořenem), pro CTE jsou důležité množiny, které získáme jako výsledek konkrétních dotazů. CTE, v podstatě, umožňuje, v rámci jednoho dotazu, vytvořit dočasnou tabulku, tuto tabulku naplnit, a tuto tabulku (resp. odkaz na ni) použít v jiné části dotazu. &amp;lt;tt&amp;gt;CONNECT BY&amp;lt;/tt&amp;gt; si můžete představit jako jakýsi hodně zvláštní JOIN, - vůči tomu CTE je vlastně neprocedurální zápis následující funkce (rekurzivní varianta):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;   i := 0;&lt;br /&gt;
   Si := SELECT0;&lt;br /&gt;
   while Si &amp;lt;&amp;gt; {}&lt;br /&gt;
   do&lt;br /&gt;
     i := i + 1;&lt;br /&gt;
     Si := SELECT1 WHERE .. IN S(i-1); -- rekurze!&lt;br /&gt;
   done;&lt;br /&gt;
   SELECT2 FROM S;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Příklad - data jsou převzata z výše uvedeného článku (každý uzel má dva potomky):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;CREATE TABLE data (&lt;br /&gt;
  id integer PRIMARY KEY,&lt;br /&gt;
  parent integer REFERENCES data(id) NULL,&lt;br /&gt;
  value varchar&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
INSERT INTO data &lt;br /&gt;
   VALUES (1, NULL, &amp;#039;root&amp;#039;), &lt;br /&gt;
          (2, 1, &amp;#039;A&amp;#039;), (3, 1, &amp;#039;B&amp;#039;),&lt;br /&gt;
          (4, 2, &amp;#039;AA&amp;#039;), (5, 2, &amp;#039;AB&amp;#039;),&lt;br /&gt;
          (6, 3, &amp;#039;BA&amp;#039;), (7, 3, &amp;#039;BB&amp;#039;),&lt;br /&gt;
          (8, 4, &amp;#039;AAA&amp;#039;), (9, 4, &amp;#039;AAB&amp;#039;),&lt;br /&gt;
          (10, 7, &amp;#039;BBA&amp;#039;), (11, 7, &amp;#039;BBB&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
postgres=# SELECT * FROM data;&lt;br /&gt;
 id | parent | value &lt;br /&gt;
----+--------+-------&lt;br /&gt;
  1 |        | root&lt;br /&gt;
  2 |      1 | A&lt;br /&gt;
  3 |      1 | B&lt;br /&gt;
  4 |      2 | AA&lt;br /&gt;
  5 |      2 | AB&lt;br /&gt;
  6 |      3 | BA&lt;br /&gt;
  7 |      3 | BB&lt;br /&gt;
  8 |      4 | AAA&lt;br /&gt;
  9 |      4 | AAB&lt;br /&gt;
 10 |      7 | BBA&lt;br /&gt;
 11 |      7 | BBB&lt;br /&gt;
(11 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dotaz s CTE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# WITH RECURSIVE t AS (SELECT *, 0 AS level &lt;br /&gt;
                                   FROM data &lt;br /&gt;
                                  WHERE value = &amp;#039;root&amp;#039; &lt;br /&gt;
                                  UNION ALL &lt;br /&gt;
                                  SELECT data.*, level + 1 &lt;br /&gt;
                                   FROM data &lt;br /&gt;
                                        JOIN &lt;br /&gt;
                                        t &lt;br /&gt;
                                        ON data.parent = t.id) &lt;br /&gt;
              SELECT t.*, repeat(&amp;#039; &amp;#039;,level) || value AS formated_value &lt;br /&gt;
                 FROM t&lt;br /&gt;
                ORDER BY CASE WHEN value = &amp;#039;root&amp;#039; THEN &amp;#039;&amp;#039; ELSE repeat(&amp;#039; &amp;#039;,level) || value END; &lt;br /&gt;
 id | parent | value | level | formated_value &lt;br /&gt;
----+--------+-------+-------+----------------&lt;br /&gt;
  1 |        | root  |     0 | root&lt;br /&gt;
  2 |      1 | A     |     1 |  A&lt;br /&gt;
  4 |      2 | AA    |     2 |   AA&lt;br /&gt;
  8 |      4 | AAA   |     3 |    AAA&lt;br /&gt;
  9 |      4 | AAB   |     3 |    AAB&lt;br /&gt;
  5 |      2 | AB    |     2 |   AB&lt;br /&gt;
  3 |      1 | B     |     1 |  B&lt;br /&gt;
  6 |      3 | BA    |     2 |   BA&lt;br /&gt;
  7 |      3 | BB    |     2 |   BB&lt;br /&gt;
 10 |      7 | BBA   |     3 |    BBA&lt;br /&gt;
 11 |      7 | BBB   |     3 |    BBB&lt;br /&gt;
(11 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Zásadní je &amp;lt;tt&amp;gt;UNION ALL&amp;lt;/tt&amp;gt; uvnitř CTE. Pokud je CTE rekurzivní, pak musí obsahovat klauzuli &amp;lt;tt&amp;gt;UNION ALL&amp;lt;/tt&amp;gt;. Výsledek prvního dotazu v této klauzuli se použije pro inicializaci. Druhý dotaz se pak periodicky opakuje. Během dotazu vypočítávám sloupec &amp;lt;tt&amp;gt;level&amp;lt;/tt&amp;gt;, který se použije pro odsazení v položce &amp;lt;tt&amp;gt;formated_value&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
V jazyce PL/pgSQL by výše uvedenému dotazu odpovídala funkce:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;CREATE OR REPLACE FUNCTION tree_list()&lt;br /&gt;
   RETURNS TABLE(id integer, parent integer, value varchar, level integer, formated_value varchar) AS $$&lt;br /&gt;
DECLARE &lt;br /&gt;
  _s integer := 0;&lt;br /&gt;
  _rc integer;&lt;br /&gt;
BEGIN&lt;br /&gt;
  /* vytvoří a naplní tabulku t */&lt;br /&gt;
  CREATE TEMPORARY TABLE t AS&lt;br /&gt;
     SELECT *, 0 AS __level, _s AS step &lt;br /&gt;
        FROM data &lt;br /&gt;
       WHERE data.value = &amp;#039;root&amp;#039;;&lt;br /&gt;
  GET DIAGNOSTICS _rc = ROW_COUNT;&lt;br /&gt;
  /* &lt;br /&gt;
   * rekurzivní volání nad množinou, tj. výsledek dotazu použijeme, v následující iteraci,&lt;br /&gt;
   * jako parametr téhož dotazu&lt;br /&gt;
   */&lt;br /&gt;
  WHILE _rc &amp;gt; 0&lt;br /&gt;
  LOOP&lt;br /&gt;
    _s := _s + 1;&lt;br /&gt;
    INSERT INTO t&lt;br /&gt;
       SELECT data.*, __level + 1, _s &lt;br /&gt;
          FROM data&lt;br /&gt;
               JOIN &lt;br /&gt;
               t&lt;br /&gt;
               ON data.parent = t.id AND step = _s - 1;&lt;br /&gt;
    GET DIAGNOSTICS _rc = ROW_COUNT;&lt;br /&gt;
  END LOOP;&lt;br /&gt;
  /* závěrečné formátování */&lt;br /&gt;
  RETURN QUERY SELECT t.id, t.parent, t.value, t.__level, (repeat(&amp;#039; &amp;#039;,__level) || t.value)::varchar&lt;br /&gt;
                  FROM t&lt;br /&gt;
                 ORDER BY CASE WHEN t.value = &amp;#039;root&amp;#039; THEN &amp;#039;&amp;#039; ELSE repeat(&amp;#039; &amp;#039;,__level) || t.value END;&lt;br /&gt;
  DROP TABLE t;&lt;br /&gt;
  RETURN;&lt;br /&gt;
END;&lt;br /&gt;
$$ LANGUAGE plpgsql;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Je to věcí názoru, ale mně přijde, že CTE je určitě snazší napsat než napsat odpovídající funkci. Vyhodnocení dotazu s CTE je také rychlejší než volání funkce. Jinak, to je, k CTE, prakticky vše. Implementace CTE je, v PostgreSQL, není kompletní. Ale to, co je důležité, implementováno je. &lt;br /&gt;
&lt;br /&gt;
Kromě rekurzivní varianty CTE můžeme ještě použít nerekurzivní - princip je plus/mínus stejný. Nerekurzivní forma CTE zastupuje dočasné tabulky - viz příklad v článku [[PostgreSQL v roce 2009 (PostgreSQL 8.4)]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;h2&amp;gt;&amp;lt;i&amp;gt;Analytické dotazy&amp;lt;/i&amp;gt; (Analytické funkce, funkce nad okny (&amp;lt;i&amp;gt;window functions&amp;lt;/i&amp;gt;))&amp;lt;/h2&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Zatímco CTE je, díky své &amp;quot;procedurální povaze&amp;quot; relativně snadno pochopitelné, pochopení analytických dotazů je obtížnější. Analytické dotazy jsou možná na první pohled složité, na druhý pohled snadné, na třetí pak zrádné a teprve na čtvrtý pokus je možné porozumět tomu, co s nimi bylo zamýšleno. Kdesi v dějinách SQL se striktně dělily funkce serveru a funkce klienta. Server se staral o uložení a vyhledání dat. Klient, pak, o prezentaci dat. Proto např. SQL neobsahovalo žádné funkce pro formátování hodnot číselných typů nebo typů datum a čas. A také SQL neobsahovalo žádnou podporu pro číslování řádků. Po čase se ukázalo, že to prostě nefunguje, a že uživatelé chtějí některé funkce (typické pro prezentační vrstvu) i na serveru. Případně, že jsou ochotni tyto chybějící funkce suplovat výkonnostně šílenými SQL příkazy (založených na CROSS JOINu). Na tyto požadavky zareagovali i dodavatelé RDBMS. Různě. K dispozici jsou session proměnné (MSSQL, MySQL), případně uložené procedury (všechny vyspělejší RDBMS) a konečně analytické dotazy. Ty vznikly ve spolupráci Oracle a DB2 - a dostaly se i do standardu.&lt;br /&gt;
&lt;br /&gt;
Začnu příkladem - mějme tabulku report, která obsahuje 4 řádky. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;CREATE TABLE report(castka int, popis varchar);&lt;br /&gt;
INSERT INTO report VALUES(10,&amp;#039;housky&amp;#039;),(20, &amp;#039;rohliky&amp;#039;),( 4,&amp;#039;mleko&amp;#039;),(30,&amp;#039;maslo&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
postgres=# SELECT * FROM report;&lt;br /&gt;
 castka |  popis  &lt;br /&gt;
--------+---------&lt;br /&gt;
     10 | housky&lt;br /&gt;
     20 | rohliky&lt;br /&gt;
      4 | mleko&lt;br /&gt;
     30 | maslo&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Budeme požadovat takové zobrazení tohoto reportu, kdy řádky budou v pořadí určeném cenou, očíslované včetně sloupce podílu částky ku součtu všech částek.&lt;br /&gt;
&lt;br /&gt;
Podle otců zakladatelů bychom měli na takovýto report použít odpovídající software (Crystal reports, ...). Samotné SQL příliš nepomůže (bez podpory analytických dotazů). &lt;br /&gt;
&lt;br /&gt;
Procedurální řešení:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;CREATE OR REPLACE FUNCTION print_report()&lt;br /&gt;
   RETURNS TABLE (no integer, castka integer, popis varchar, pomer float) AS $$&lt;br /&gt;
DECLARE &lt;br /&gt;
  _celkem float := (SELECT sum(report.castka) FROM report)::float;&lt;br /&gt;
BEGIN&lt;br /&gt;
  no := 1;&lt;br /&gt;
  FOR print_report.castka, print_report.popis &lt;br /&gt;
    IN SELECT report.castka, report.popis FROM report ORDER BY report.castka DESC&lt;br /&gt;
  LOOP&lt;br /&gt;
    pomer := print_report.castka / _celkem;&lt;br /&gt;
    RETURN NEXT;&lt;br /&gt;
    no := no + 1;&lt;br /&gt;
  END LOOP;&lt;br /&gt;
  RETURN;&lt;br /&gt;
END;&lt;br /&gt;
$$ LANGUAGE plpgsql;&lt;br /&gt;
 &lt;br /&gt;
postgres=# SELECT no, castka, popis, to_char(pomer*100, &amp;#039;90.99&amp;#039;) &lt;br /&gt;
              FROM print_report();&lt;br /&gt;
 no | castka |  popis  | to_char &lt;br /&gt;
----+--------+---------+---------&lt;br /&gt;
  1 |     30 | maslo   |  46.88&lt;br /&gt;
  2 |     20 | rohliky |  31.25&lt;br /&gt;
  3 |     10 | housky  |  15.62&lt;br /&gt;
  4 |      4 | mleko   |   6.25&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Přestože to není složitý kód, dají se v něm udělat chyby (hlavní riziko je v kolizi jmen sloupců a proměnných). Pokud by databáze podporovala analytické dotazy, lze pro získání stejného reportu použít následující SQL příkaz:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (ORDER BY castka DESC) AS no, castka, popis, to_char(castka/sum(castka::float) OVER ()*100,&amp;#039;90.99&amp;#039;) &lt;br /&gt;
              FROM report &lt;br /&gt;
             ORDER BY castka DESC;&lt;br /&gt;
 no | castka |  popis  | to_char &lt;br /&gt;
----+--------+---------+---------&lt;br /&gt;
  1 |     30 | maslo   |  46.88&lt;br /&gt;
  2 |     20 | rohliky |  31.25&lt;br /&gt;
  3 |     10 | housky  |  15.62&lt;br /&gt;
  4 |      4 | mleko   |   6.25&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Výsledek je stejný, ale bez procedurálního programování (a z toho plynoucích potenciálních chyb). Ve výše uvedeném SQL dotazu je použitá analytická funkce &amp;lt;tt&amp;gt;row_number()&amp;lt;/tt&amp;gt; a agregační funkce &amp;lt;tt&amp;gt;sum()&amp;lt;/tt&amp;gt; jako analytická funkce (agregační funkce můžeme použít jako analytické funkce). Analytickou funkci poznáme podle zápisu &amp;lt;tt&amp;gt;func(params) OVER (specifikace okna)&amp;lt;/tt&amp;gt;. Analytická funkce se počítá nad definovaným oknem a výsledek se zapisuje zpět do téhož okna. Okno nám zobrazuje určitý výsek relace (podobně jako u agregací, nedochází tu však k redukci řádků). Pro funkci row_number určujeme, že vstupem funkce má být celá tabulka report řazená podle částky od největšího k nejmenšímu. Pozor - tady je zádrhel - jestli jsem to správně pochopil, tak &amp;lt;tt&amp;gt;ORDER BY&amp;lt;/tt&amp;gt; se v analytických funkcích neváže na výstup, ale na vstup funkce. Analytické funkce se vyhodnocují před seřazením výstupu, tj. před klauzulí &amp;lt;tt&amp;gt;ORDER BY&amp;lt;/tt&amp;gt;!&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (), castka FROM report; -- dostaneme co chceme&lt;br /&gt;
 row_number | castka &lt;br /&gt;
------------+--------&lt;br /&gt;
          1 |     10&lt;br /&gt;
          2 |     20&lt;br /&gt;
          3 |      4&lt;br /&gt;
          4 |     30&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Jenomže, pokud přidáme klasickou klauzuli &amp;lt;tt&amp;gt;ORDER BY&amp;lt;/tt&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (), castka FROM report ORDER BY castka;&lt;br /&gt;
 row_number | castka &lt;br /&gt;
------------+--------&lt;br /&gt;
          3 |      4&lt;br /&gt;
          1 |     10&lt;br /&gt;
          2 |     20&lt;br /&gt;
          4 |     30&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
tak dostaneme výsledek, který asi nebudeme chtít. Klauzule &amp;lt;tt&amp;gt;ORDER BY&amp;lt;/tt&amp;gt; se aplikovala nejen na skutečná data, ale i na výsledek analytické funkce. Pokud budeme požadovat &amp;quot;správný výsledek&amp;quot; musíme:&lt;br /&gt;
&lt;br /&gt;
a) změnit pořadí vyhodnocování jednotlivých klauzulí:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (), castka FROM (SELECT castka FROM report ORDER BY castka) x;&lt;br /&gt;
 row_number | castka &lt;br /&gt;
------------+--------&lt;br /&gt;
          1 |      4&lt;br /&gt;
          2 |     10&lt;br /&gt;
          3 |     20&lt;br /&gt;
          4 |     30&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b) určit funkci &amp;lt;tt&amp;gt;row_number()&amp;lt;/tt&amp;gt; nad seřazenými daty:&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (ORDER BY castka DESC), castka FROM report ORDER BY castka DESC;&lt;br /&gt;
 row_number | castka &lt;br /&gt;
------------+--------&lt;br /&gt;
          1 |     30&lt;br /&gt;
          2 |     20&lt;br /&gt;
          3 |     10&lt;br /&gt;
          4 |      4&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Je poměrně důležité si uvědomit, že &amp;lt;tt&amp;gt;ORDER BY&amp;lt;/tt&amp;gt; pro analytickou funkci (v definici okna) znamená trochu něco jiného než klauzule &amp;lt;tt&amp;gt;ORDER BY&amp;lt;/tt&amp;gt; příkazu &amp;lt;tt&amp;gt;SELECT&amp;lt;/tt&amp;gt;. Přičemž ovšem nedochází k více násobné (a v tomto případě, zbytečné) operaci sort. Viz prováděcí plán:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# EXPLAIN VERBOSE SELECT row_number() OVER (ORDER BY castka DESC), castka FROM report ORDER BY castka DESC;&lt;br /&gt;
                            QUERY PLAN                            &lt;br /&gt;
------------------------------------------------------------------&lt;br /&gt;
 WindowAgg  (cost=1.08..1.15 rows=4 width=4)&lt;br /&gt;
   Output: row_number() OVER (?), castka&lt;br /&gt;
   -&amp;gt;  Sort  (cost=1.08..1.09 rows=4 width=4)&lt;br /&gt;
         Output: castka&lt;br /&gt;
         Sort Key: castka&lt;br /&gt;
         -&amp;gt;  Seq Scan on report  (cost=0.00..1.04 rows=4 width=4)&lt;br /&gt;
               Output: castka&lt;br /&gt;
(7 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pro ukázky složitějších dotazů potřebujeme o něco málo komplexnější data. Znovu použiji tabulku report rozšířenou o sloupec kategorie:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;CREATE TABLE report(castka int, popis varchar, kategorie varchar);&lt;br /&gt;
INSERT INTO report &lt;br /&gt;
   VALUES (10,&amp;#039;housky&amp;#039;,&amp;#039;pecivo&amp;#039;),(20, &amp;#039;rohliky&amp;#039;,&amp;#039;pecivo&amp;#039;),(15,&amp;#039;chleb&amp;#039;,&amp;#039;pecivo&amp;#039;),&lt;br /&gt;
          (4,&amp;#039;mleko&amp;#039;,&amp;#039;mlecne vyr.&amp;#039;),(30,&amp;#039;maslo&amp;#039;,&amp;#039;mlecne vyr.&amp;#039;),&lt;br /&gt;
          (40,&amp;#039;spekacky&amp;#039;,&amp;#039;uzeniny&amp;#039;),(50,&amp;#039;klobasy&amp;#039;,&amp;#039;uzeniny&amp;#039;),(80,&amp;#039;polican&amp;#039;,&amp;#039;uzeniny&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
postgres=# SELECT * FROM report;&lt;br /&gt;
 castka |  popis   |  kategorie  &lt;br /&gt;
--------+----------+-------------&lt;br /&gt;
     10 | housky   | pecivo&lt;br /&gt;
     20 | rohliky  | pecivo&lt;br /&gt;
     15 | chleb    | pecivo&lt;br /&gt;
      4 | mleko    | mlecne vyr.&lt;br /&gt;
     30 | maslo    | mlecne vyr.&lt;br /&gt;
     40 | spekacky | uzeniny&lt;br /&gt;
     50 | klobasy  | uzeniny&lt;br /&gt;
     80 | polican  | uzeniny&lt;br /&gt;
(8 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
V definici okna můžeme použít klauzuli &amp;lt;i&amp;gt;&amp;lt;tt&amp;gt;PARTITION BY&amp;lt;/tt&amp;gt;&amp;lt;/i&amp;gt;. Je to něco na způsob &amp;lt;tt&amp;gt;GROUP BY&amp;lt;/tt&amp;gt;. Vytváří to určité oblasti definované shodou vybrané hodnoty, příp. vybraných hodnot.&lt;br /&gt;
&lt;br /&gt;
Začnu známou funkcí row_number. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (PARTITION BY kategorie), * FROM report;&lt;br /&gt;
 row_number | castka |  popis   |  kategorie  &lt;br /&gt;
------------+--------+----------+-------------&lt;br /&gt;
          1 |      4 | mleko    | mlecne vyr.&lt;br /&gt;
          2 |     30 | maslo    | mlecne vyr.&lt;br /&gt;
          1 |     15 | chleb    | pecivo&lt;br /&gt;
          2 |     10 | housky   | pecivo&lt;br /&gt;
          3 |     20 | rohliky  | pecivo&lt;br /&gt;
          1 |     40 | spekacky | uzeniny&lt;br /&gt;
          2 |     50 | klobasy  | uzeniny&lt;br /&gt;
          3 |     80 | polican  | uzeniny&lt;br /&gt;
(8 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Přidám agregační funkci count() použitou jako analytickou (window) funkci:&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT row_number() OVER (PARTITION BY kategorie), count(*) OVER (PARTITION BY kategorie), * FROM report;&lt;br /&gt;
 row_number | count | castka |  popis   |  kategorie  &lt;br /&gt;
------------+-------+--------+----------+-------------&lt;br /&gt;
          1 |     2 |      4 | mleko    | mlecne vyr.&lt;br /&gt;
          2 |     2 |     30 | maslo    | mlecne vyr.&lt;br /&gt;
          1 |     3 |     15 | chleb    | pecivo&lt;br /&gt;
          2 |     3 |     10 | housky   | pecivo&lt;br /&gt;
          3 |     3 |     20 | rohliky  | pecivo&lt;br /&gt;
          1 |     3 |     40 | spekacky | uzeniny&lt;br /&gt;
          2 |     3 |     50 | klobasy  | uzeniny&lt;br /&gt;
          3 |     3 |     80 | polican  | uzeniny&lt;br /&gt;
(8 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Agregační funkce &amp;quot;počítá&amp;quot; tak, jak bychom čekali. Jenomže, výsledkem, není jeden řádek, nýbrž několik řádků - výsledek agregační funkce se rozepíše do příslušných buněk. Dotaz se poměrně natahuje - opakuje se mi definice okna &amp;quot;&amp;lt;tt&amp;gt;OVER (PARTITION BY kategorie)&amp;lt;/tt&amp;gt;&amp;quot;. Obranou je použití tzv. pojmenovaného okna:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;SELECT row_number() OVER w, count(*) OVER w, * &lt;br /&gt;
   FROM report &lt;br /&gt;
  WINDOW w AS (PARTITION BY kategorie);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Zajímavé jsou funkce, které &amp;quot;hodnotí&amp;quot; jednotlivé řádky. Začnu funkcemi &amp;lt;tt&amp;gt;rank()&amp;lt;/tt&amp;gt; a &amp;lt;tt&amp;gt;dense_rank()&amp;lt;/tt&amp;gt;. Abych demonstroval rozdíl mezi nimi, tak přidám ještě jeden řádek do tabulky report. Ten záměrně obsahuje částku odpovídající některé existující částce v určené kategorii:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;INSERT INTO report VALUES (80,&amp;#039;herkules&amp;#039;,&amp;#039;uzeniny&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
postgres=# SELECT * FROM report WHERE kategorie = &amp;#039;uzeniny&amp;#039;;&lt;br /&gt;
 castka |  popis   | kategorie &lt;br /&gt;
--------+----------+-----------&lt;br /&gt;
     40 | spekacky | uzeniny&lt;br /&gt;
     50 | klobasy  | uzeniny&lt;br /&gt;
     80 | polican  | uzeniny&lt;br /&gt;
     80 | herkules | uzeniny&lt;br /&gt;
(4 rows)&lt;br /&gt;
&lt;br /&gt;
postgres=# SELECT row_number() OVER w, rank() OVER w, dense_rank() OVER w, * &lt;br /&gt;
              FROM report &lt;br /&gt;
             WHERE kategorie = &amp;#039;uzeniny&amp;#039; &lt;br /&gt;
             WINDOW w AS (ORDER BY castka DESC);&lt;br /&gt;
 row_number | rank | dense_rank | castka |  popis   | kategorie &lt;br /&gt;
------------+------+------------+--------+----------+-----------&lt;br /&gt;
          1 |    1 |          1 |     80 | polican  | uzeniny&lt;br /&gt;
          2 |    1 |          1 |     80 | herkules | uzeniny&lt;br /&gt;
          3 |    3 |          2 |     50 | klobasy  | uzeniny&lt;br /&gt;
          4 |    4 |          3 |     40 | spekacky | uzeniny&lt;br /&gt;
(4 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
O první pozici (podle ceny) se dělí poličan a herkules. Funkce &amp;lt;tt&amp;gt;row_number()&amp;lt;/tt&amp;gt; tento fakt vůbec nebere v potaz (na rozdíl od &amp;lt;tt&amp;gt;rank()&amp;lt;/tt&amp;gt; a &amp;lt;tt&amp;gt;dense_rank()&amp;lt;/tt&amp;gt;). rank i dense_rank umístí obě zmíněné uzeniny na první místo. Pro nás zajímavé je druhé a třetí místo. Když je něco na prvním místě, tak logicky, to co je za tím musí být na druhém místě (pro funkci dense_rank). Pokud bychom ale chtěli vědět, kolikátý je to řádek, pak smysluplnější výsledek nám poskytne funkce rank. Funkce rank se perfektně hodí pro řešení klasické úlohy nej podle kategorie viz článek [http://www.postgres.cz/index.php/Korelovan%C3%A9_vno%C5%99en%C3%A9_dotazy#V.C3.BDb.C4.9Br_prvn.C3.ADch_.28posledn.C3.ADch.29_n_produkt.C5.AF_s_ka.C5.BEd.C3.A9_skupiny Korelované vnořené dotazy: proč nepoužívat a čím nahradit].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;CREATE TABLE zamestnanci(prijmeni varchar, profese varchar, mzda integer);&lt;br /&gt;
INSERT INTO zamestnanci  &lt;br /&gt;
   VALUES(&amp;#039;Stěhule&amp;#039;,&amp;#039;analytik&amp;#039;,10000),&lt;br /&gt;
         (&amp;#039;Kůs&amp;#039;,&amp;#039;analytik&amp;#039;,15000),&lt;br /&gt;
         (&amp;#039;Nováková&amp;#039;,&amp;#039;asistentka&amp;#039;,8000),&lt;br /&gt;
         (&amp;#039;Vlčková&amp;#039;,&amp;#039;asistentka&amp;#039;, 10000),&lt;br /&gt;
         (&amp;#039;Kabuďa&amp;#039;,&amp;#039;PR&amp;#039;,16000),&lt;br /&gt;
         (&amp;#039;Jirkovský&amp;#039;,&amp;#039;analytik&amp;#039;,7000);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dotaz na nejlépe placeného zaměstnance v každé kategorii (je nezbytné si pomoci derivovanou tabulkou, window funkce nelze použít v klauzuli &amp;lt;tt&amp;gt;WHERE&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT prijmeni, profese, mzda &lt;br /&gt;
              FROM (SELECT rank() OVER (PARTITION BY profese ORDER BY mzda DESC), * &lt;br /&gt;
                       FROM zamestnanci) x &lt;br /&gt;
             WHERE x.rank = 1;&lt;br /&gt;
 prijmeni |  profese   | mzda  &lt;br /&gt;
----------+------------+-------&lt;br /&gt;
 Kůs      | analytik   | 15000&lt;br /&gt;
 Vlčková  | asistentka | 10000&lt;br /&gt;
 Kabuďa   | PR         | 16000&lt;br /&gt;
(3 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pokud by mne zajímali dva nejlépe placení zaměstnanci, pak použiji funkci dense_rank a podmínku &amp;lt;tt&amp;gt;dense_rank &amp;amp;lt;= 2&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Další zajímavou funkcí jsou mezisoučty:&lt;br /&gt;
&amp;lt;pre&amp;gt;postgres=# SELECT sum(castka) OVER () AS total, sum(castka) OVER (PARTITION BY kategorie) AS sum_groupby, &lt;br /&gt;
                  sum(castka) OVER (ORDER BY kategorie ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total_cum, &lt;br /&gt;
                  sum(castka) OVER (PARTITION BY kategorie ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum, &lt;br /&gt;
                  castka, popis, kategorie &lt;br /&gt;
              FROM report ;&lt;br /&gt;
 total | sum_groupby | total_cum | cum | castka |  popis   |  kategorie  &lt;br /&gt;
-------+-------------+-----------+-----+--------+----------+-------------&lt;br /&gt;
   329 |          34 |         4 |   4 |      4 | mleko    | mlecne vyr.&lt;br /&gt;
   329 |          34 |        34 |  34 |     30 | maslo    | mlecne vyr.&lt;br /&gt;
   329 |          45 |        49 |  15 |     15 | chleb    | pecivo&lt;br /&gt;
   329 |          45 |        59 |  25 |     10 | housky   | pecivo&lt;br /&gt;
   329 |          45 |        79 |  45 |     20 | rohliky  | pecivo&lt;br /&gt;
   329 |         250 |       119 |  40 |     40 | spekacky | uzeniny&lt;br /&gt;
   329 |         250 |       169 |  90 |     50 | klobasy  | uzeniny&lt;br /&gt;
   329 |         250 |       249 | 170 |     80 | polican  | uzeniny&lt;br /&gt;
   329 |         250 |       329 | 250 |     80 | herkules | uzeniny&lt;br /&gt;
(9 rows)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
V posledním příkladu se uplatnila klauzule &amp;lt;i&amp;gt;&amp;lt;tt&amp;gt;ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW&amp;lt;/tt&amp;gt;&amp;lt;/i&amp;gt;, která určuje, že okno má být od začátku oblasti, do aktuálně zpracovávaného řádku. Podpora analytických funkcí není kompletní (v 8.4), proto, pozor, pokud byste chtěli portovat aplikace s DB2 nebo Oracle. Určitě doporučuji si projít [http://www.postgresql.org/docs/8.4/static/functions-window.html#FUNCTIONS-WINDOW-TABLE dokumentaci], kde je seznam všech aktuálně implementovaných window funkcí. Pokud byste si chtěli napsat vlastní funkce tohoto typu, pak jedině v jazyce C.  &lt;br /&gt;
&lt;br /&gt;
Podpora analytických funkcí je v PostgreSQL &amp;quot;žhavou&amp;quot; novinkou. Nepochybně bude pár měsíců trvat, možná let, trvat než se s ní aplikační vývojáři sžijí. Přesto věřím, že si své příznivce najde a řadě lidí ušetří práci a čas. &lt;br /&gt;
&lt;br /&gt;
Co je mi známo, tak CTE a analytické funkce podporují všechny velké komerční databáze - Oracle a DB2 dlouho, MSSQL od verze 2005. PostgreSQL je první open source databází, kde jsou tyto funkce dostupné. Aktuálně v [http://www.postgresql.org/about/news.1074 betě 8.4], kterou si můžete volně stáhnout a nainstalovat (pro většinu operačních systémů) z adresy http://www.postgresql.org/ftp/binary/v8.4beta/http  (Linux - hledejte v rpm repozitory). Přehledu nových funkcí verze 8.4 se věnoval článek [[PostgreSQL v roce 2009 (PostgreSQL 8.4)]]. Plná verze 8.4 by měla být dostupná do začátku prázdnin.&lt;/div&gt;</summary>
		<author><name>imported&gt;Pavel</name></author>
	</entry>
</feed>