Základy tabulkových výrazů, 2. část – Odvozené tabulky, logické úvahy
Minulý měsíc jsem poskytl pozadí k tabulkovým výrazům v T-SQL. Vysvětlil jsem souvislosti z relační teorie a standardu SQL. Vysvětlil jsem, jak je tabulka v SQL pokusem o reprezentaci vztahu z relační teorie. Vysvětlil jsem také, že relační výraz je výraz fungující na jedné nebo více relacích jako vstupech a vedoucí k relaci. Podobně v SQL je tabulkový výraz výraz fungující na jedné nebo více vstupních tabulkách, jehož výsledkem je tabulka. Výraz může být dotaz, ale také nemusí. Výraz může být například konstruktorem hodnot tabulky, jak vysvětlím později v tomto článku. Vysvětlil jsem také, že v této sérii se zaměřuji na čtyři konkrétní typy pojmenovaných tabulkových výrazů, které T-SQL podporuje:odvozené tabulky, běžné tabulkové výrazy (CTE), pohledy a vložené funkce s hodnotou tabulky (TVF).
Pokud už nějakou dobu pracujete s T-SQL, pravděpodobně jste narazili na nemálo případů, kdy jste buď museli používat tabulkové výrazy, nebo to bylo nějak pohodlnější ve srovnání s alternativními řešeními, která je nepoužívají. Zde je jen několik příkladů případů použití, které vás napadnou:
Vytvořte modulární řešení rozdělením složitých úkolů do kroků, z nichž každý představuje jiný tabulkový výraz.
Míchání výsledků seskupených dotazů a podrobností v případě, že se pro tento účel rozhodnete nepoužívat funkce okna.
Logické zpracování dotazů zpracovává klauzule dotazu v následujícím pořadí:FROM>WHERE>GROUP BY>HAVING>SELECT>ORDER BY. Výsledkem je, že na stejné úrovni vnoření jsou aliasy sloupců, které definujete v klauzuli SELECT, dostupné pouze pro klauzuli ORDER BY. Nejsou dostupné pro ostatní klauzule dotazu. Pomocí tabulkových výrazů můžete znovu použít aliasy, které definujete ve vnitřním dotazu v jakékoli klauzuli vnějšího dotazu, a vyhnout se tak opakování zdlouhavých/složitých výrazů.
Funkce okna se mohou objevit pouze v klauzulích SELECT a ORDER BY dotazu. Pomocí tabulkových výrazů můžete přiřadit alias výrazu založenému na funkci okna a poté tento alias použít v dotazu proti tabulkovému výrazu.
Operátor PIVOT zahrnuje tři prvky:seskupování, šíření a agregaci. Tento operátor identifikuje prvek seskupení implicitně eliminací. Pomocí tabulkového výrazu můžete promítnout přesně ty tři prvky, které by se měly týkat, a nechat vnější dotaz použít tabulkový výraz jako vstupní tabulku operátoru PIVOT, a tak řídit, který prvek je seskupovacím prvkem.
Úpravy s TOP nepodporují klauzuli ORDER BY. Můžete řídit, které řádky budou vybrány nepřímo, definováním tabulkového výrazu založeného na dotazu SELECT s filtrem TOP nebo OFFSET-FETCH a klauzulí ORDER BY a aplikováním modifikace na tabulkový výraz.
Toto zdaleka není vyčerpávající seznam. Některé z výše uvedených případů použití a další ukážu v této sérii. Jen jsem zde chtěl zmínit některé případy použití, abych ilustroval, jak důležité jsou tabulkové výrazy v našem kódu T-SQL a proč se vyplatí investovat do dobrého pochopení jejich základů.
V tomto článku se zaměřím na logické zpracování odvozených tabulek konkrétně.
Ve svých příkladech použiji ukázkovou databázi nazvanou TSQLV5. Skript, který jej vytváří a naplňuje, najdete zde a jeho ER diagram zde.
Odvozené tabulky
Termín odvozená tabulka se v SQL a T-SQL používá s více než jedním významem. Nejprve chci tedy ujasnit, na který z nich v tomto článku odkazuji. Mám na mysli konkrétní jazykovou konstrukci, kterou obvykle, ale nejen, definujete v klauzuli FROM vnějšího dotazu. Brzy poskytnu syntaxi této konstrukce.
Obecnější použití termínu odvozená tabulka v SQL je protějškem odvozeného vztahu z relační teorie. Odvozená relace je relace výsledku, která je odvozena z jednoho nebo více vstupních základních relací aplikací relačních operátorů z relační algebry, jako je projekce, průnik a další, na tyto základní vztahy. Podobně, v obecném smyslu, odvozená tabulka v SQL je výsledková tabulka, která je odvozena z jedné nebo více základních tabulek vyhodnocením výrazů proti těmto vstupním základním tabulkám.
Kromě toho jsem zkontroloval, jak standard SQL definuje základní tabulku, a okamžitě mě mrzelo, že jsem se obtěžoval.
4.15.2 Základní tabulky
Základní tabulka je buď trvalá základní tabulka, nebo dočasná tabulka.
Trvalá základní tabulka je buď běžná trvalá základní tabulka nebo tabulka se systémovou verzí.
Běžná základní tabulka je buď běžná trvalá základní tabulka, nebo dočasná tabulka.“
Přidáno sem bez dalších komentářů…
V T-SQL můžete vytvořit základní tabulku pomocí příkazu CREATE TABLE, ale existují i jiné možnosti, např. SELECT INTO a DECLARE @T AS TABLE.
Zde je definice standardu pro odvozené tabulky v obecném smyslu:
4.15.3 Odvozené tabulky
Odvozená tabulka je tabulka odvozená přímo nebo nepřímo z jedné nebo více jiných tabulek vyhodnocením výrazu, jako je , , nebo . může obsahovat nepovinné . Pořadí řádků tabulky určené je zaručeno pouze pro , který bezprostředně obsahuje .“
O odvozených tabulkách v obecném smyslu je zde třeba poznamenat několik zajímavých věcí. Jeden má co do činění s komentářem o objednávce. K tomu se dostanu později v článku. Další je, že odvozená tabulka v SQL může být platným samostatným tabulkovým výrazem, ale nemusí. Například následující výraz představuje odvozenou tabulku a je také považován za platný samostatný tabulkový výraz (můžete jej spustit):
SELECT custid, companynameFROM Sales.CustomersWHERE country =N'USA'
A naopak, následující výraz představuje odvozenou tabulku, ale není platný samostatný tabulkový výraz:
VNITŘNÍ PŘIPOJENÍ T1 T2 NA T1.keycol =T2.keycol
T-SQL podporuje řadu tabulkových operátorů, které poskytují odvozenou tabulku, ale nejsou podporovány jako samostatné výrazy. Jsou to:PŘIPOJIT SE, PIVOT, UNPIVOT a APPLY. Potřebujete klauzuli, ve které budou fungovat (obvykle FROM, ale také klauzuli USING příkazu MERGE) a hostitelský dotaz.
Od této chvíle budu používat termín odvozená tabulka k popisu specifičtějšího jazykového konstruktu, nikoli v obecném smyslu popsaném výše.
Syntaxe
Odvozenou tabulku lze definovat jako součást vnějšího příkazu SELECT v jeho klauzuli FROM. Může být také definován jako součást příkazů DELETE a UPDATE v jejich klauzuli FROM a jako součást příkazu MERGE v jeho klauzuli USING. Další podrobnosti o syntaxi při použití v příkazech modifikace poskytnu později v tomto článku.
Zde je syntaxe pro zjednodušený dotaz SELECT proti odvozené tabulce:
VYBRAT Z ( ) [ AS ] [ () ];
Definice odvozené tabulky se objeví tam, kde se může normálně objevit základní tabulka, v klauzuli FROM vnějšího dotazu. Může to být vstup pro operátora tabulky, jako je JOIN, APPLY, PIVOT a UNPIVOT. Při použití jako správný vstup pro operátor APPLY může část
odvozené tabulky mít korelace se sloupci z vnější tabulky (více o tom ve vyhrazeném budoucím článku v této sérii). Jinak musí být tabulkový výraz samostatný.
Vnější příkaz může mít všechny obvyklé dotazovací prvky. V případě příkazu SELECT:WHERE, GROUP BY, HAVING, ORDER BY a jak již bylo zmíněno, tabulkové operátory v klauzuli FROM.
Zde je příklad jednoduchého dotazu na odvozenou tabulku představující zákazníky z USA:
SELECT custid, companynameFROM ( SELECT custid, companyname FROM Sales.Customers WHERE country =N'USA' ) AS UC;
V příkazu obsahujícím odvozenou definici tabulky je třeba identifikovat tři hlavní části:
Tabulkový výraz (vnitřní dotaz)
Odvozený název tabulky nebo přesněji to, co se v teorii relací považuje za proměnnou rozsahu
Vnější prohlášení
Tabulkový výraz má představovat tabulku a jako takový musí splňovat určité požadavky, které běžný dotaz nutně splňovat nemusí. Podrobnosti poskytnu brzy v sekci „Tabulkový výraz je tabulka“.
Pokud jde o název odvozené cílové tabulky; běžným předpokladem mezi vývojáři T-SQL je, že jde pouze o název nebo alias, který přiřadíte cílové tabulce. Podobně zvažte následující dotaz:
SELECT custid, companynameFROM Sales.Customers AS CWHERE country =N'USA';
Také zde je společným předpokladem, že AS C je pouze způsob, jak přejmenovat nebo alias, tabulku Zákazníci pro účely tohoto dotazu, počínaje krokem zpracování logického dotazu, kde je jméno přiřazeno, a dále. Z hlediska teorie vztahů však to, co C představuje, má hlubší význam. C je to, co je známé jako proměnná rozsahu. C je odvozená relační proměnná, která se pohybuje přes n-tice ve vstupní relační proměnné Zákazníci. Ve výše uvedeném příkladu se C pohybuje nad n-ticemi v Zákazníkech a vyhodnocuje predikát země =N'USA'. N-tice, pro které je predikát vyhodnocen jako pravdivý, se stávají součástí výsledné relace C.
Tabulkový výraz je tabulka
S ohledem na pozadí, které jsem doposud uvedl, by to, co se chystám vysvětlit dále, mělo být malým překvapením. část odvozené definice tabulky je tabulka . Je tomu tak, i když je to vyjádřeno jako dotaz. Pamatujete na uzavírací vlastnost relační algebry? Totéž platí pro zbytek výše uvedených pojmenovaných tabulkových výrazů (CTE, pohledy a inline TVF). Jak jste se již dozvěděli, tabulka SQL je protějškem vztahu relační teorie , i když ne dokonalým protějškem. Tabulkový výraz tedy musí splňovat určité požadavky, aby bylo zajištěno, že výsledkem je tabulka – takové, které dotaz, který není použit jako tabulkový výraz, nutně nemusí. Zde jsou tři konkrétní požadavky:
Všechny sloupce tabulkového výrazu musí mít názvy
Všechny názvy sloupců tabulkového výrazu musí být jedinečné
Řádky tabulkového výrazu nemají pořadí
Pojďme si tyto požadavky rozebrat jeden po druhém a probrat význam jak pro relační teorii, tak pro SQL.
Všechny sloupce musí mít názvy
Pamatujte, že vztah má nadpis a tělo. Záhlaví relace je sada atributů (sloupců v SQL). Atribut má název a název typu a je identifikován svým názvem. Dotaz, který se nepoužívá jako tabulkový výraz, nemusí nutně přiřazovat názvy všem cílovým sloupcům. Jako příklad zvažte následující dotaz:
SELECT empid, jméno, příjmení, CONCAT_WS(N'/', země, region, město)FROM HR.Employees;
Tento dotaz generuje následující výstup:
empid jméno příjmení (žádný název sloupce)------ ---------- ---------- -------------- ----1 Sara Davis USA/WA/Seattle2 Don Funk USA/WA/Tacoma3 Judy Lew USA/WA/Kirkland4 Yael Peled USA/WA/Redmond5 Sven Mortensen Velká Británie/Londýn6 Paul Suurs Velká Británie/Londýn7 Russell King Velká Británie/Londýn8 Maria Cameron USA/WA/Seattle9 Patricia Doyle UK/Londýn
Výstup dotazu má anonymní sloupec, který je výsledkem zřetězení atributů umístění pomocí funkce CONCAT_WS. (Mimochodem, tato funkce byla přidána do SQL Server 2017, takže pokud spouštíte kód ve starší verzi, můžete tento výpočet nahradit alternativním výpočtem dle vašeho výběru.) Tento dotaz tedy vrátit tabulku, nemluvě o vztahu. Proto není platné používat takový dotaz jako část tabulkového výrazu/vnitřního dotazu v odvozené definici tabulky.
Zkuste to:
SELECT *FROM ( SELECT empid, jméno, příjmení, CONCAT_WS(N'/', země, region, město) FROM HR.Employees ) AS D;
Zobrazí se následující chyba:
Zpráva 8155, úroveň 16, stav 2, řádek 50 Pro sloupec 4 „D“ nebyl zadán žádný název sloupce.
Mimochodem, všimli jste si něčeho zajímavého na chybové zprávě? Stěžuje si na sloupec 4 a zdůrazňuje rozdíl mezi sloupci v SQL a atributy v relační teorii.
Řešením je samozřejmě ujistit se, že sloupcům, které jsou výsledkem výpočtů, explicitně přiřazujete názvy. T-SQL podporuje několik technik pojmenování sloupců. Zmíním se o dvou z nich.
Můžete použít techniku inline pojmenování, kdy po výpočtu přiřadíte název cílového sloupce a volitelnou klauzuli AS, jako v < expression > [ AS ] < column name > , asi takhle:
SELECT empid, jméno, příjmení, custlocationFROM ( SELECT empid, jméno, příjmení, CONCAT_WS(N'/', země, region, město) AS custlocation FROM HR.Employees ) AS D;
Tento dotaz generuje následující výstup:
empid jméno příjmení vlastní umístění------ ---------- ---------- ----------------1 Sara Davis USA/WA/Seattle2 Don Funk USA/WA/Tacoma3 Judy Lew USA/WA/Kirkland4 Yael Peled USA/WA/Redmond5 Sven Mortensen Velká Británie/Londýn6 Paul Suurs Velká Británie/Londýn7 Russell King Velká Británie/Londýn8 Maria Cameron USA/WA/Seattle9 Patricia Doyle UK/Londýn
Pomocí této techniky je velmi snadné při kontrole kódu zjistit, který název cílového sloupce je přiřazen ke kterému výrazu. Také stačí pojmenovat sloupce, které jinak názvy ještě nemají.
Můžete také použít více externí techniku pojmenování sloupců, kde zadáte názvy cílových sloupců v závorkách hned za odvozeným názvem tabulky, například takto:
S touto technikou však musíte uvést názvy všech sloupců – včetně těch, které již názvy mají. Přiřazení názvů cílových sloupců se provádí podle pozice, zleva doprava, tj. název prvního cílového sloupce představuje první výraz v seznamu SELECT vnitřního dotazu; název druhého cílového sloupce představuje druhý výraz; a tak dále.
Všimněte si, že v případě nekonzistence mezi názvy vnitřních a vnějších sloupců, řekněme kvůli chybě v kódu, je rozsahem vnitřních názvů vnitřní dotaz – nebo přesněji proměnná vnitřního rozsahu (zde implicitně HR.Employees Zaměstnanci AS)—a rozsahem vnějších jmen je proměnná vnějšího rozsahu (v našem případě D). Je to trochu více zapojeno do určování rozsahu názvů sloupců, které souvisí s logickým zpracováním dotazů, ale to je položka pro pozdější diskusi.
Potenciál pro chyby v externí syntaxi pojmenování je nejlépe vysvětlen na příkladu.
Prohlédněte si výstup předchozího dotazu s úplnou sadou zaměstnanců z tabulky HR.Employees. Poté zvažte následující dotaz a před jeho spuštěním se pokuste zjistit, které zaměstnance očekáváte ve výsledku:
SELECT empid, jméno, příjmení, custlocationFROM ( SELECT empid, jméno, příjmení, CONCAT_WS(N'/', země, region, město) FROM HR.Employees WHERE příjmení LIKE N'D%' ) AS D(empid, příjmení, jméno, vlastní umístění)WHERE jméno LIKE N'D%';
Pokud očekáváte, že dotaz vrátí prázdnou sadu pro daná ukázková data, protože v současné době nejsou žádní zaměstnanci s příjmením i jménem začínajícím na písmeno D, chybí vám chyba v kódu.
Nyní spusťte dotaz a prozkoumejte skutečný výstup:
empid jméno příjmení vlastní umístění------ ---------- --------- ---------------1 Davis Sara USA/WA/Seattle9 Doyle Patricia UK/Londýn
Co se stalo?
Vnitřní dotaz určuje jméno jako druhý sloupec a příjmení jako třetí sloupec v seznamu SELECT. Kód, který přiřazuje názvy cílových sloupců odvozené tabulky ve vnějším dotazu, určuje druhé příjmení a třetí jméno. Kódové jméno jako příjmení a příjmení jako jméno v proměnné rozsahu D. Ve skutečnosti pouze filtrujete zaměstnance, jejichž příjmení začíná písmenem D. Nefiltrujete zaměstnance s příjmením a křestním jménem, které začínají s písmenem D.
Syntaxe inline aliasingu není náchylná k takovým chybám. Za prvé, normálně nevytváříte alias sloupce, který již má název, se kterým jste spokojeni. Za druhé, i když chcete přiřadit jiný alias pro sloupec, který již má název, není příliš pravděpodobné, že se syntaxí AS přiřadíte nesprávný alias. Přemýšlejte o tom; jaká je pravděpodobnost, že budete psát takto:
SELECT empid, firstname, lastname, custlocationFROM ( SELECT empid AS empid, firstname AS lastname, lastname AS firstname, CONCAT_WS(N'/', country, region, town) AS custlocation FROM HR.Employees WHERE lastname LIKE N'D %' ) AS DWHERE křestní jméno LIKE N'D%';
Očividně to není příliš pravděpodobné.
Všechny názvy sloupců musí být jedinečné
Zpět ke skutečnosti, že záhlaví relace je sada atributů, a vzhledem k tomu, že atribut je identifikován jménem, musí být názvy atributů pro stejný vztah jedinečné. V daném dotazu můžete vždy odkazovat na atribut pomocí dvoudílného názvu s názvem proměnné rozsahu jako kvalifikátorem, jako v .. Pokud je název sloupce bez kvalifikátoru jednoznačný, můžete předponu názvu proměnné rozsahu vynechat. Co je však důležité si zapamatovat, je to, co jsem řekl dříve o rozsahu názvů sloupců. V kódu, který zahrnuje pojmenovaný tabulkový výraz, s vnitřním dotazem (tabulkovým výrazem) i vnějším dotazem, je rozsah názvů sloupců ve vnitřním dotazu proměnnými vnitřního rozsahu a rozsah názvů sloupců ve vnějším dotazu dotaz jsou proměnné vnějšího rozsahu. Pokud vnitřní dotaz zahrnuje více zdrojových tabulek se stejným názvem sloupce, můžete na tyto sloupce stále jednoznačně odkazovat přidáním názvu proměnné rozsahu jako předpony. Pokud název proměnné rozsahu nepřiřadíte explicitně, získáte jej implicitně, jako byste použili AS .
Jako příklad zvažte následující samostatný dotaz:
VYBERTE C.custid, O.custid, O.orderidFROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid;
Tento dotaz se nezdaří s chybou duplicitního názvu sloupce, protože jeden custid sloupec se ve skutečnosti jmenuje C.custid a druhý O.custid v rozsahu aktuálního dotazu. Tento dotaz generuje následující výstup:
Zkuste však tento dotaz použít jako tabulkový výraz v definici odvozené tabulky s názvem CO, například takto:
SELECT *FROM ( SELECT C.custid, O.custid, O.orderid FROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;
Pokud jde o vnější dotaz, máte jednu proměnnou rozsahu s názvem CO a rozsah všech názvů sloupců ve vnějším dotazu je tato proměnná rozsahu. Názvy všech sloupců v dané proměnné rozsahu (nezapomeňte, že proměnná rozsahu je relační proměnná) musí být jedinečné. Proto se zobrazí následující chyba:
Msg 8156, Level 16, State 1, Line 80 Sloupec 'custid' byl pro 'CO' zadán vícekrát.
Opravou je samozřejmě přiřazení různých názvů sloupců ke dvěma cuctid sloupcům, pokud jde o proměnnou rozsahu CO, například takto:
SELECT *FROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid FROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;
Pokud se budete řídit osvědčenými postupy, explicitně uvedete názvy sloupců v seznamu SELECT nejvzdálenějšího dotazu. Protože se jedná pouze o jednu proměnnou rozsahu, nemusíte pro odkazy na vnější sloupce používat dvoudílný název. Pokud si přejete použít dvoudílný název, přidejte před názvy sloupců název proměnné vnějšího rozsahu CO, například takto:
SELECT CO.custcustid, CO.ordercustid, CO.orderidFROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid FROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;
Žádná objednávka
O pojmenovaných tabulkových výrazech a řazení toho musím říct docela dost – dost na článek sám o sobě – takže tomuto tématu věnuji budoucí článek. Přesto jsem se zde chtěl krátce dotknout tématu, protože je tak důležité. Připomeňme, že tělo relace je množinou n-tic a podobně je tělo tabulky množinou řádků. Sada nemá pořadí. SQL přesto umožňuje, aby nejvzdálenější dotaz měl klauzuli ORDER BY sloužící významu uspořádání prezentace, jak ukazuje následující dotaz:
SELECT orderid, valFROM Sales.OrderValuesORDER BY val DESC;
Musíte však pochopit, že tento dotaz ve výsledku nevrací vztah. Ani z pohledu SQL dotaz nevrací tabulku jako výsledek, a proto není považován za tabulkový výraz. V důsledku toho je neplatné použít takový dotaz jako část tabulkového výrazu v odvozené definici tabulky.
Zkuste spustit následující kód:
SELECT orderid, valFROM ( SELECT orderid, val FROM Sales.OrderValues ORDER BY val DESC ) AS D;
Zobrazí se následující chyba:
Zpráva 1033, Úroveň 15, Stav 1, Řádek 124 Klauzule ORDER BY je neplatná v pohledech, vložených funkcích, odvozených tabulkách, poddotazech a běžných tabulkových výrazech, pokud není specifikováno také TOP, OFFSET nebo FOR XML.
Budu se zabývat pokud část chybové zprávy.
Pokud chcete, aby nejvzdálenější dotaz vrátil seřazený výsledek, musíte v nejvzdálenějším dotazu zadat klauzuli ORDER BY, například takto:
SELECT orderid, valFROM ( SELECT orderid, val FROM Sales.OrderValues ) AS DORDER BY val DESC;
Pokud jde o pokud část chybové zprávy; T-SQL podporuje proprietární filtr TOP i standardní filtr OFFSET-FETCH. Oba filtry se spoléhají na klauzuli ORDER BY ve stejném oboru dotazu, která pro ně definuje, které horní řádky se mají filtrovat. To je bohužel výsledek pasti v návrhu těchto funkcí, která neodděluje pořadí prezentací od řazení filtrů. Ať je to jakkoli, jak Microsoft se svým filtrem TOP, tak i standard s filtrem OFFSET-FETCH umožňují zadat klauzuli ORDER BY ve vnitřním dotazu, pokud také specifikuje filtr TOP nebo OFFSET-FETCH. Tento dotaz je tedy platný, například:
SELECT orderid, valFROM ( SELECT TOP (3) orderid, value FROM Sales.OrderValues ORDER BY val DESC ) AS D;
Když jsem na svém systému spustil tento dotaz, vygeneroval následující výstup:
číslo objednávky-------- ---------10865 16387.5010981 15810.0011030 12615.05
Je však důležité zdůraznit, že jediným důvodem, proč je ve vnitřním dotazu povolena klauzule ORDER BY, je podpora filtru TOP. To je jediná záruka, kterou dostanete, pokud jde o objednávku. Vzhledem k tomu, že vnější dotaz nemá ani klauzuli ORDER BY, nezískáte z tohoto dotazu záruku na žádné konkrétní uspořádání prezentace, bez ohledu na pozorované chování. To je případ T-SQL i standardu. Zde je citace ze standardu, který se zabývá touto částí:
"Pořadí řádků tabulky určené je zaručeno pouze pro , který bezprostředně obsahuje ."
Jak již bylo zmíněno, o tabulkových výrazech a řazení je toho třeba říci mnohem více, čemuž se budu věnovat v budoucím článku. Poskytnu také příklady, které demonstrují, jak absence klauzule ORDER BY ve vnějším dotazu znamená, že nezískáte žádné záruky objednání prezentace.
Takže tabulkový výraz, např. vnitřní dotaz v odvozené definici tabulky, je tabulka. Podobně samotná odvozená tabulka (ve specifickém smyslu) je také tabulkou. Není to základní stůl, ale přesto je to stůl. Totéž platí pro CTE, zobrazení a inline TVF. Nejsou to základní tabulky, spíše odvozené (v obecnějším smyslu), ale přesto jsou to tabulky.
Konstrukční nedostatky
Odvozené stoly mají dva hlavní nedostatky ve svém designu. Obojí souvisí se skutečností, že odvozená tabulka je definována v klauzuli FROM vnějšího dotazu.
Jedna konstrukční chyba souvisí se skutečností, že pokud potřebujete dotazovat odvozenou tabulku z vnějšího dotazu a následně tento dotaz použít jako tabulkový výraz v jiné definici odvozené tabulky, skončíte vnořením těchto odvozených dotazů na tabulku. Ve výpočetní technice má explicitní vnoření kódu zahrnující více úrovní vnoření tendenci vést ke složitému kódu, který se obtížně udržuje.
Zde je velmi základní příklad, který to demonstruje:
VYBERTE rok objednávky, numcustsFROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(datum objednávky) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2WHERE numcusts> 70;
Tento kód vrací roky objednávky a počet zákazníků, kteří zadali objednávky během každého roku, pouze pro roky, kdy počet zákazníků, kteří zadali objednávky, byl větší než 70.
Hlavní motivací pro použití tabulkových výrazů je zde možnost odkazovat na alias sloupce vícekrát. Nejvnitřnější dotaz použitý jako tabulkový výraz pro odvozenou tabulku D1 se dotazuje na tabulku Sales.Orders a přiřazuje název sloupce orderyear k výrazu YEAR(orderdate) a také vrací custid sloupec. Dotaz na D1 seskupuje řádky z D1 podle orderyear a vrací orderyear, stejně jako zřetelný počet zákazníků, kteří zadali objednávky během daného roku alias numcusty. Kód definuje odvozenou tabulku nazvanou D2 na základě tohoto dotazu. Nejvzdálenější dotaz než dotazy D2 a filtruje pouze roky, kde počet zákazníků, kteří zadali objednávky, byl větší než 70.
Pokus o kontrolu tohoto kódu nebo jeho odstraňování v případě problémů je ošemetný kvůli více úrovním vnoření. Namísto toho, abyste si kód prohlíželi přirozenějším způsobem shora dolů, musíte ho analyzovat od nejvnitřnější jednotky a postupně jít ven, protože to je praktičtější.
Smyslem použití odvozených tabulek v tomto příkladu bylo zjednodušit kód tím, že se vyhneme nutnosti opakovat výrazy. Nejsem si ale jistý, že toto řešení tohoto cíle dosáhne. V tomto případě je pravděpodobně lepší některé výrazy zopakovat, aniž byste museli používat odvozené tabulky úplně, například takto:
VYBERTE ROK(datum objednávky) JAKO rok objednávky, POČET (DISTINCT Custid) JAKO numcustsFROM Sales.OrdersGROUP BY YEAR(datum objednávky)HAVING COUNT(DISTINCT custid)> 70;
Mějte na paměti, že zde pro ilustraci uvádím velmi jednoduchý příklad. Představte si produkční kód s více úrovněmi vnoření a s delším a propracovanějším kódem a uvidíte, jak se jeho údržba podstatně zkomplikuje.
Další chyba v návrhu odvozených tabulek souvisí s případy, kdy potřebujete pracovat s více instancemi stejné odvozené tabulky. Jako příklad zvažte následující dotaz:
VYBERTE CUR.orderyear, CUR.čísla, CUR.čísla - PRV.čísla JAKO diffFROM ( VYBERTE ROK(datum objednávky) JAKO rok objednávky, POČET(*) JAKO čísla Z Prodeje.Objednávky SKUPINA PODLE ROKŮ(datum objednávky) ) JAKO CUR VLEVO OUTER JOIN ( VYBERTE ROK (datum objednávky) JAKO rok objednávky, POČET (*) JAKO čísla Z Prodeje. Objednávky SKUPINA PODLE ROKŮ (datum objednávky) ) JAKO PRV ON CUR.orderyear =PRV.orderyear + 1;
Tento kód počítá počet objednávek zpracovaných v každém roce a také rozdíl oproti předchozímu roku. Ignorujte skutečnost, že existují jednodušší způsoby, jak dosáhnout stejného úkolu pomocí funkcí okna – tento kód používám k ilustraci určitého bodu, takže samotný úkol a různé způsoby jeho řešení nejsou důležité.
Spojení je operátor tabulky, který zachází se svými dvěma vstupy jako s množinou – to znamená, že mezi nimi není žádné pořadí. Označují se jako levý a pravý vstup, takže jeden z nich (nebo oba) můžete označit jako zachovanou tabulku ve vnějším spojení, ale přesto mezi nimi není žádný první a druhý. Je povoleno používat odvozené tabulky jako vstupy spojení, ale název proměnné rozsahu, který přiřadíte levému vstupu, není v definici pravého vstupu dostupný. Je to proto, že oba jsou koncepčně definovány ve stejném logickém kroku, jako by ve stejném časovém okamžiku. V důsledku toho při spojování odvozených tabulek nemůžete definovat dvě proměnné rozsahu založené na jednom tabulkovém výrazu. Bohužel musíte kód opakovat a definovat dvě proměnné rozsahu na základě dvou identických kopií kódu. To samozřejmě komplikuje udržovatelnost kódu a zvyšuje pravděpodobnost chyb. Každá změna, kterou provedete v jednom tabulkovém výrazu, musí být aplikována i na druhý.
Jak vysvětlím v budoucím článku, CTE ve svém návrhu nemají tyto dvě chyby, které mají odvozené tabulky.
Konstruktor hodnot tabulky
Konstruktor hodnot tabulky vám umožňuje sestavit hodnotu tabulky na základě samostatných skalárních výrazů. Takovou tabulku pak můžete použít ve vnějším dotazu, stejně jako používáte odvozenou tabulku, která je založena na vnitřním dotazu. V budoucím článku pojednávám o laterálních odvozených tabulkách a korelace podrobně a ukážu sofistikovanější formy konstruktorů hodnot tabulek. V tomto článku se však zaměřím na jednoduchou formu, která je založena čistě na samostatných skalárních výrazech.
The general syntax for a query against a table value constructor is as follows: