PostgreSQL: 13: 7.8. S dotazy (Common Table Expressions)

základní hodnotou SELECT v WITH je rozdělení komplikovaných dotazů na jednodušší části. Příkladem je:

WITH regional_sales AS ( SELECT region, SUM(amount) AS total_sales FROM orders GROUP BY region), top_regions AS ( SELECT region FROM regional_sales WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales))SELECT region, product, SUM(quantity) AS product_units, SUM(amount) AS product_salesFROM ordersWHERE region IN (SELECT region FROM top_regions)GROUP BY region, product;

který zobrazuje součty prodejů za produkt pouze v nejlepších prodejních regionech. WITH klauzule definuje dvě pomocné prohlášení jménem regional_sales a top_regions, kde výstup regional_sales se používá v top_regions a výstupu top_regions se používá v primární SELECT dotaz. Tento příklad by mohl být napsán bez WITH, ale potřebovali bychom dvě úrovně vnořených sub – SELECT s. Je to trochu jednodušší sledovat tímto způsobem.

volitelný modifikátor RECURSIVE změní WITH z pouhého syntaktického pohodlí na funkci, která dosahuje věcí, které nejsou jinak možné ve standardním SQL. Pomocí RECURSIVE může dotaz WITH odkazovat na vlastní výstup. Velmi jednoduchým příkladem je tento dotaz pro součet celých čísel od 1 do 100:

WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100)SELECT sum(n) FROM t;

obecné podobě rekurzivní WITH dotaz je vždy non-rekurzivní termín, pak UNION (nebo UNION ALL), pak rekurzivní horizontu, kde jen rekurzivní výraz může obsahovat odkaz na dotaz je vlastní výstup. Takový dotaz se provádí následovně:

hodnocení rekurzivních dotazů

  1. vyhodnoťte nerekurzivní termín. Pro UNION (ale ne UNION ALL) zlikvidujte duplicitní řádky. Zahrňte všechny zbývající řádky do výsledku rekurzivního dotazu a také je umístěte do dočasné pracovní tabulky.

  2. Tak dlouho, jak na pracovní stůl není prázdný, opakujte tyto kroky:

    1. Vyhodnocení rekurzivní výraz, nahrazující aktuální obsah pracovní stůl pro rekurzivní self-reference. Pro UNION (ale ne UNION ALL) zlikvidujte duplicitní řádky a řádky, které duplikují jakýkoli předchozí řádek výsledku. Zahrňte všechny zbývající řádky do výsledku rekurzivního dotazu a také je umístěte do dočasné mezilehlé tabulky.

    2. nahraďte obsah pracovního stolu obsahem mezilehlé tabulky a poté mezilehlou tabulku vyprázdněte.

Poznámka

přísně vzato, tento proces není iterací, ale RECURSIVE je terminologie zvolená výborem pro standardy SQL.

ve výše uvedeném příkladu má pracovní tabulka v každém kroku pouze jeden řádek a v postupných krocích přebírá hodnoty od 1 do 100. Ve 100. kroku neexistuje žádný výstup kvůli klauzuli WHERE, a tak dotaz končí.

rekurzivní dotazy se obvykle používají k řešení hierarchických nebo stromově strukturovaných dat. Užitečným příkladem je tento dotaz najít všechny přímé a nepřímé dílčích částí produktu, vzhledem k tomu, pouze tabulku, která ukazuje okamžitou inkluze:

WITH RECURSIVE included_parts(sub_part, part, quantity) AS ( SELECT sub_part, part, quantity FROM parts WHERE part = 'our_product' UNION ALL SELECT p.sub_part, p.part, p.quantity FROM included_parts pr, parts p WHERE p.part = pr.sub_part)SELECT sub_part, SUM(quantity) as total_quantityFROM included_partsGROUP BY sub_part

Při práci s rekurzivní dotazy, je důležité, aby být jisti, že rekurzivní část dotazu se nakonec vrátí žádná n-tice, nebo jiný dotaz, bude smyčka na dobu neurčitou. Někdy může použití UNION namísto UNION ALL dosáhnout tím, že vyřadí řádky, které duplikují předchozí výstupní řádky. Nicméně, často cyklus nezahrnuje výstup řádky, které jsou zcela duplicitní: může být nutné zkontrolovat jeden nebo několik polí, jestli stejném místě byla předtím. Standardní metodou pro řešení takových situací je výpočet pole již navštívených hodnot. Zvažte například následující dotaz, který prohledá tabulku graph pomocí link pole:

WITH RECURSIVE search_graph(id, link, data, depth) AS ( SELECT g.id, g.link, g.data, 1 FROM graph g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1 FROM graph g, search_graph sg WHERE g.id = sg.link)SELECT * FROM search_graph;

Tento dotaz bude informovat, pokud se link vztahy obsahují cykly. Protože požadujeme výstup “hloubka”, jen změna UNION ALL na UNION by nevylučovala opakování. Místo toho musíme rozpoznat, zda jsme znovu dosáhli stejného řádku při sledování konkrétní cesty odkazů. Přidáme dva sloupce path a cycle loop-náchylné dotaz:

WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS ( SELECT g.id, g.link, g.data, 1, ARRAY, false FROM graph g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1, path || g.id, g.id = ANY(path) FROM graph g, search_graph sg WHERE g.id = sg.link AND NOT cycle)SELECT * FROM search_graph;

Kromě prevence cykly, pole, hodnota je často užitečné v jeho vlastní pravý jako zastupující “cestu” k dosažení nějaké konkrétní řádek.

v obecném případě, kdy je třeba zkontrolovat více než jedno pole, abyste rozpoznali cyklus, použijte pole řádků. Například, pokud jsme potřebovali porovnat pole f1 a f2:

WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS ( SELECT g.id, g.link, g.data, 1, ARRAY, false FROM graph g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1, path || ROW(g.f1, g.f2), ROW(g.f1, g.f2) = ANY(path) FROM graph g, search_graph sg WHERE g.id = sg.link AND NOT cycle)SELECT * FROM search_graph;

Tip

Vynechat ROW() syntaxe v obvyklém případě, kdy pouze jedno pole musí být zaškrtnuto, aby uznaly cyklu. To umožňuje použít spíše jednoduché pole než pole kompozitního typu, čímž se získá účinnost.

Tip

algoritmus hodnocení rekurzivního dotazu produkuje svůj výstup v pořadí Vyhledávání v první šíři. Můžete zobrazit výsledky v prohledávání do hloubky, aby tím, že vnější dotazu ORDER BY “cesta” sloupec postavené tímto způsobem.

užitečným trikem pro testování dotazů, pokud si nejste jisti, zda mohou smyčky, je umístit LIMIT do nadřazeného dotazu. Například, tento dotaz by smyčka navždy, bez LIMIT:

WITH RECURSIVE t(n) AS ( SELECT 1 UNION ALL SELECT n+1 FROM t)SELECT n FROM t LIMIT 100;

To funguje, protože PostgreSQL je provádění vyhodnocuje pouze tolik řádků, WITH dotaz, jak jsou skutečně přitažené za vlasy, mateřské dotaz. Použití tohoto triku ve výrobě se nedoporučuje, protože jiné systémy mohou fungovat jinak. Také to obvykle nebude fungovat, pokud provedete vnější dotaz třídit výsledky rekurzivního dotazu nebo je připojit k jiné tabulce, protože v takových případech se vnější dotaz obvykle pokusí načíst veškerý výstup dotazu WITH.

užitečných vlastností WITH dotazy je, že jsou obvykle hodnoceny pouze jednou na exekuce mateřské dotaz, i když jsou uvedené více než jednou mateřskou dotaz nebo sourozenec WITH dotazy. Drahé výpočty, které jsou potřebné na více místech, lze tedy umístit do dotazu WITH, aby se zabránilo redundantní práci. Další možnou aplikací je zabránit nežádoucímu vícenásobnému hodnocení funkcí s vedlejšími účinky. Nicméně, na druhé straně této mince je, že optimizer nebude schopen tlačit omezení z nadřazeného dotazu do množit odkazy WITH dotaz, protože to může mít vliv na všechny použití WITH výstup dotazu, když by to mělo mít vliv pouze jeden. Dotaz s více odkazy WITH bude vyhodnocen jako napsaný, bez potlačení řádků, které by Nadřazený dotaz mohl později zahodit. (Jak je však uvedeno výše, hodnocení se může zastavit dříve, Pokud odkaz(y) Na dotaz vyžaduje pouze omezený počet řádků.

pokud je však dotaz WITH rekurzivní a bez vedlejších účinků (to znamená, že je SELECT neobsahující žádné Volatilní funkce), lze jej složit do nadřazeného dotazu, což umožňuje společnou optimalizaci dvou úrovní dotazu. Ve výchozím nastavení se to stane, pokud Nadřazený dotaz odkazuje na dotaz WITH pouze jednou, ale ne pokud odkazuje na dotaz WITH více než jednou. Můžete přepsat toto rozhodnutí zadáním MATERIALIZED vynutit samostatný výpočet WITH dotaz, nebo zadáním NOT MATERIALIZED vynutit být sloučeny do mateřské dotaz. Druhá volba riskuje duplicitní výpočet dotazu WITH, ale stále může poskytnout čisté úspory, pokud každé použití dotazu WITH potřebuje pouze malou část úplného výstupu dotazu WITH.

jednoduchý příklad těchto pravidel je

WITH w AS ( SELECT * FROM big_table)SELECT * FROM w WHERE key = 123;

WITH dotaz bude složen, produkující stejný plán spuštění jako

SELECT * FROM big_table WHERE key = 123;

zejména, pokud je index na key, to bude pravděpodobně být použit k načtení pouze řádky s key = 123. Na druhou stranu, v

WITH w AS ( SELECT * FROM big_table)SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.refWHERE w2.key = 123;

WITH dotaz bude zhmotnil, produkující dočasnou kopii big_table, který je pak spojil s sám — bez nároku na jakýkoli index. Tento dotaz bude proveden mnohem efektivněji, pokud je zapsán jako

WITH w AS NOT MATERIALIZED ( SELECT * FROM big_table)SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.refWHERE w2.key = 123;

, takže omezení nadřazeného dotazu lze použít přímo na skenování big_table.

příkladem, kde NOT MATERIALIZED může být nežádoucí, je

WITH w AS ( SELECT key, very_expensive_function(val) as f FROM some_table)SELECT * FROM w AS w1 JOIN w AS w2 ON w1.f = w2.f;

zde materializace dotazu WITH zajišťuje, že very_expensive_function je vyhodnoceno pouze jednou na řádek tabulky, nikoli dvakrát.

výše uvedené příklady ukazují pouze použití WITH s SELECT, ale lze jej připojit stejným způsobem k INSERT, UPDATE nebo DELETE. V každém případě účinně poskytuje dočasné tabulky,na které lze odkazovat v hlavním příkazu.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.