sql >> Databáze >  >> RDS >> Database

Základy tabulkových výrazů, část 9 – Pohledy, srovnání s odvozenými tabulkami a CTE

Toto je 9. díl série o pojmenovaných tabulkových výrazech. V části 1 jsem poskytl pozadí pojmenovaných tabulkových výrazů, které zahrnují odvozené tabulky, běžné tabulkové výrazy (CTE), pohledy a vložené tabulkové funkce (iTVF). Ve 2. části, 3. části a 4. části jsem se zaměřil na odvozené tabulky. V části 5, části 6, části 7 a části 8 jsem se zaměřil na CTE. Jak jsem vysvětlil, odvozené tabulky a CTE jsou výrazy pojmenované tabulky v rozsahu příkazů. Jakmile skončí prohlášení, které je definuje, jsou pryč.

Nyní jsme připraveni přistoupit k pokrytí opakovaně použitelných pojmenovaných tabulkových výrazů. To znamená takové, které jsou vytvořeny jako objekt v databázi a zůstanou tam trvale, dokud nejsou odstraněny. Jako takové jsou přístupné a znovu použitelné všem, kteří mají správná oprávnění. Do této kategorie spadají zhlédnutí a iTVF. Rozdíl mezi těmito dvěma je především v tom, že první nepodporuje vstupní parametry a druhé ano.

V tomto článku začínám pokrytí zhlédnutí. Stejně jako předtím se nejprve zaměřím na logické nebo koncepční aspekty a později přistoupím k aspektům optimalizace. S prvním článkem o pohledech chci začít odlehčeně, zaměřit se na to, co je pohled, za použití správné terminologie a porovnat úvahy o návrhu pohledů s dříve diskutovanými odvozenými tabulkami a CTE.

Ve svých příkladech použiji ukázkovou databázi nazvanou TSQLV5. Skript, který jej vytváří a naplňuje, naleznete zde a jeho ER diagram zde.

Co je zobrazení?

Jako obvykle, když diskutujeme o teorii vztahů, nám, praktikujícím SQL, často říkáme, že terminologie, kterou používáme, je špatná. Takže v tomto duchu, hned od začátku, začnu tím, že když použijete výraz tabulky a pohledy , je to špatné. Naučil jsem se to od Chrise Date.

Připomeňme, že tabulka je protějškem SQL k relaci (trochu zjednodušuje diskuzi o hodnotách a proměnných). Tabulkou může být základní tabulka definovaná jako objekt v databázi, nebo to může být tabulka vrácená výrazem – konkrétněji tabulkovým výrazem. To je podobné tomu, že relace by mohla být ta, která je vrácena z relačního výrazu. Tabulkovým výrazem může být dotaz.

Co je to pohled? Je to pojmenovaný tabulkový výraz, podobně jako CTE je pojmenovaný tabulkový výraz. Je to tak, jak jsem řekl, pohled je opakovaně použitelný pojmenovaný tabulkový výraz, který je vytvořen jako objekt v databázi a je přístupný těm, kteří mají správná oprávnění. To je vše, co říká, pohled je stůl. Není to základní stůl, ale přesto stůl. Takže stejně jako by se zdálo divné říkat „obdélník a čtverec“ nebo „whisky a Lagavulin“ (pokud jste neměli příliš mnoho Lagavulina!), použití „tabulek a pohledů“ je stejně nevhodné.

Syntaxe

Zde je syntaxe T-SQL pro příkaz CREATE VIEW:

CREATE [ OR ALTER ] VIEW [ . ] [ () ]
[ S ]
JAKO

[ S MOŽNOSTÍ KONTROLY ]
[; ]

Příkaz CREATE VIEW musí být prvním a jediným příkazem v dávce.

Všimněte si, že část CREATE OR ALTER byla představena v SQL Server 2016 SP1, takže pokud používáte dřívější verzi, budete muset pracovat se samostatnými příkazy CREATE VIEW a ALTER VIEW v závislosti na tom, zda objekt již existuje nebo ne. Jak pravděpodobně dobře víte, změna existujícího objektu zachovává přidělená oprávnění. To je jeden z důvodů, proč je obvykle rozumné změnit existující objekt, než jej upustit a znovu vytvořit. Některé lidi překvapí, že změna pohledu nezachová stávající atributy pohledu; pokud je chcete zachovat, je třeba je specifikovat.

Zde je příklad jednoduché definice zobrazení představující zákazníky z USA:

USE TSQLV5;
GO
 
CREATE OR ALTER VIEW Sales.USACustomers
AS
  SELECT custid, companyname
  FROM Sales.Customers
  WHERE country = N'USA';
GO

A zde je prohlášení, které dotazuje pohled:

SELECT custid, companyname
FROM Sales.USACustomers;

Mezi příkazem, který vytváří pohled, a příkazem, který se na něj dotazuje, najdete úplně stejné tři prvky, které jsou součástí příkazu proti odvozené tabulce nebo CTE:

  1. Výraz vnitřní tabulky (vnitřní dotaz pohledu)
  2. Přiřazený název tabulky (název zobrazení)
  3. Příkaz s vnějším dotazem proti zobrazení

Ti z vás s bystrým pohledem si jistě všimli, že se zde ve skutečnosti jedná o dva tabulkové výrazy. Je tu vnitřní (vnitřní dotaz pohledu) a je tu vnější (dotaz v příkazu proti pohledu). V příkazu s dotazem proti pohledu je dotaz samotný tabulkovým výrazem a jakmile přidáte terminátor, stane se z něj příkaz. Může to znít vybíravě, ale pokud to pochopíte a nazvete věci pravými jmény, odrazí se to na vašich znalostech. A není skvělé, když víte, že to víte?

Také všechny požadavky z tabulkového výrazu v odvozených tabulkách a CTE, o kterých jsme hovořili dříve v řadě, platí pro tabulkový výraz, na kterém je pohled založen. Připomínáme, že požadavky jsou:

  • 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í

Pokud potřebujete obnovit své znalosti o tom, co se skrývá za těmito požadavky, přečtěte si část „Tabulkový výraz je tabulka“ v části 2 této série. Ujistěte se, že rozumíte zejména části „bez objednávky“. Pro krátké připomenutí, tabulkový výraz je tabulka a jako taková nemá žádné pořadí. Proto nemůžete vytvořit pohled na základě dotazu s klauzulí ORDER BY, pokud tato klauzule nepodporuje filtr TOP nebo OFFSET-FETCH. A i s touto výjimkou, která umožňuje vnitřnímu dotazu mít klauzuli ORDER BY, chcete si pamatovat, že pokud vnější dotaz proti pohledu nemá vlastní klauzuli ORDER BY, nezískáte záruku, že se dotaz vrátí řádky v libovolném pořadí, bez ohledu na pozorované chování. To je velmi důležité pochopit!

Vnoření a více referencí

Když jsem probíral úvahy o návrhu odvozených tabulek a CTE, porovnal jsem tyto dva z hlediska vnořování a vícenásobných referencí. Nyní se podívejme, jak si názory v těchto odděleních vedou. Začnu hnízděním. Za tímto účelem porovnáme kód, který vrací roky, ve kterých více než 70 zákazníků zadalo objednávky pomocí odvozených tabulek, CTE a zobrazení. Kód s odvozenými tabulkami a CTE jste již viděli dříve v sérii. Zde je kód, který zpracovává úlohu pomocí odvozených tabulek:

SELECT orderyear, numcusts
FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
       FROM ( SELECT YEAR(orderdate) AS orderyear, custid
              FROM Sales.Orders ) AS D1
       GROUP BY orderyear ) AS D2
WHERE numcusts > 70;

Poukázal jsem na to, že hlavní nevýhodou, kterou zde u odvozených tabulek vidím, je skutečnost, že vnořujete definice odvozených tabulek, což může vést ke složitosti při pochopení, údržbě a odstraňování problémů s takovým kódem.

Zde je kód, který zpracovává stejný úkol pomocí CTE:

WITH C1 AS
(
  SELECT YEAR(orderdate) AS orderyear, custid
  FROM Sales.Orders
),
C2 AS
(
  SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  FROM C1
  GROUP BY orderyear
)
SELECT orderyear, numcusts
FROM C2
WHERE numcusts > 70;

Poukázal jsem na to, že mi to připadá jako mnohem jasnější kód kvůli nedostatku vnoření. Každý krok řešení můžete vidět od začátku do konce samostatně v jeho vlastní jednotce, přičemž logika řešení jasně plyne shora dolů. Možnost CTE proto v tomto ohledu vidím jako vylepšení oproti odvozeným tabulkám.

Nyní k pohledům. Pamatujte, že jednou z hlavních výhod pohledů je opětovná použitelnost. Můžete také ovládat přístupová oprávnění. Vývoj zúčastněných jednotek je trochu podobnější CTE v tom smyslu, že svou pozornost můžete od začátku do konce soustředit na jednu jednotku. Navíc máte možnost se flexibilně rozhodnout, zda vytvoříte samostatný pohled na jednotku v řešení, nebo třeba jen jeden pohled na základě dotazu zahrnujícího výrazy pojmenované tabulky v rozsahu příkazů.

Pokud musí být každá jednotka znovu použitelná, šli byste s prvním. Zde je kód, který byste v takovém případě použili a vytvořili tři zobrazení:

-- Sales.OrderYears
CREATE OR ALTER VIEW Sales.OrderYears
AS 
  SELECT YEAR(orderdate) AS orderyear, custid
  FROM Sales.Orders;
GO
 
-- Sales.YearlyCustCounts
CREATE OR ALTER VIEW Sales.YearlyCustCounts
AS
  SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  FROM Sales.OrderYears
  GROUP BY orderyear;
GO
 
-- Sales.YearlyCustCountsMin70
CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70
AS
  SELECT orderyear, numcusts
  FROM Sales.YearlyCustCounts
  WHERE numcusts > 70;
GO

Můžete se dotazovat na každý z pohledů nezávisle, ale zde je kód, který byste použili k vrácení původní úlohy.

SELECT orderyear, numcusts
FROM Sales.YearlyCustCountsAbove70;

Pokud existuje požadavek na opětovné použití pouze pro nejvzdálenější část (co vyžadoval původní úkol), není ve skutečnosti potřeba vyvíjet tři různé pohledy. Můžete vytvořit jeden pohled na základě dotazu zahrnujícího CTE nebo odvozené tabulky. Zde je návod, jak to udělat s dotazem zahrnujícím CTE:

CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70
AS
  WITH C1 AS
  (
    SELECT YEAR(orderdate) AS orderyear, custid
    FROM Sales.Orders
  ),
  C2 AS
  (
    SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
    FROM C1
    GROUP BY orderyear
  )
  SELECT orderyear, numcusts
  FROM C2
  WHERE numcusts > 70;
GO

Mimochodem, pokud to nebylo zřejmé, CTE, na kterých je založen vnitřní dotaz pohledu, mohou být rekurzivní.

Pojďme k případům, kdy potřebujete více odkazů na stejný tabulkový výraz z vnějšího dotazu. Úkolem tohoto příkladu je vypočítat počet ročních objednávek za rok a porovnat počet v každém roce s rokem předchozím. Nejjednodušší způsob, jak toho dosáhnout, je ve skutečnosti použít funkci okna MAS, ale my použijeme spojení mezi dvěma instancemi tabulkového výrazu představujícího počet ročních objednávek, abychom porovnali případ s více referencemi mezi třemi nástroji.

Toto je kód, který jsme dříve v sérii používali ke zpracování úlohy s odvozenými tabulkami:

SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
       FROM Sales.Orders
       GROUP BY YEAR(orderdate) ) AS CUR
  LEFT OUTER JOIN
     ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
       FROM Sales.Orders
       GROUP BY YEAR(orderdate) ) AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Zde je velmi jasná nevýhoda. Definici tabulkového výrazu musíte zopakovat dvakrát. V podstatě definujete dva pojmenované tabulkové výrazy založené na stejném kódu dotazu.

Zde je kód, který zpracovává stejný úkol pomocí CTE:

WITH OrdCount AS
(
  SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
  FROM Sales.Orders
  GROUP BY YEAR(orderdate)
)
SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM OrdCount AS CUR
  LEFT OUTER JOIN OrdCount AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Je zde jasná výhoda; definujete pouze jeden pojmenovaný tabulkový výraz založený na jedné instanci vnitřního dotazu a dvakrát na něj odkazujete z vnějšího dotazu.

Zobrazení jsou v tomto smyslu více podobná CTE. Definujete pouze jeden pohled na základě pouze jedné kopie dotazu, například takto:

CREATE OR ALTER VIEW Sales.YearlyOrderCounts
AS
  SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
  FROM Sales.Orders
  GROUP BY YEAR(orderdate);
GO

Ale lepší než u CTE, nejste omezeni na opětovné použití pojmenovaného tabulkového výrazu pouze ve vnějším příkazu. Název zobrazení můžete opakovaně použít, kolikrát chcete, s libovolným počtem nesouvisejících dotazů, pokud máte správná oprávnění. Zde je kód k dosažení úkolu pomocí více odkazů na zobrazení:

SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM Sales.YearlyOrderCounts AS CUR
  LEFT OUTER JOIN Sales.YearlyOrderCounts AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Zdá se, že zobrazení jsou více podobná CTE než odvozeným tabulkám, s další funkcí, která je více znovupoužitelným nástrojem se schopností řídit oprávnění. Nebo abychom to otočili, je pravděpodobně vhodné uvažovat o CTE jako o pohledu v rozsahu prohlášení. Co by teď mohlo být opravdu úžasné, je, kdybychom měli také pojmenovaný tabulkový výraz s širším rozsahem, než je rozsah CTE, užší než rozsah pohledu. Nebylo by například skvělé, kdybychom měli pojmenovaný tabulkový výraz s rozsahem na úrovni relace?

Shrnutí

miluji toto téma. V tabulkových výrazech je toho tolik, co má kořeny v relační teorii, která má zase kořeny v matematice. Miluji vědět, jaké jsou správné termíny pro věci, a obecně se ujišťuji, že mám základy pečlivě vymyšlené, i když se to někomu může zdát jako vybíravé a příliš pedantské. Když se podívám zpět na svůj proces učení v průběhu let, vidím velmi jasnou cestu mezi trváním na dobrém uchopení základů, používáním správné terminologie a skutečným poznáním svých věcí později, až se dostaneme k mnohem pokročilejším a složitějším věcem.

Jaké jsou tedy kritické části, pokud jde o názory?

  • Výhled je tabulka.
  • Je to tabulka, která je odvozena z dotazu (tabulkový výraz).
  • Je mu přidělen název, který se uživateli jeví jako název tabulky, protože se jedná o název tabulky.
  • Je vytvořen jako trvalý objekt v databázi.
  • Můžete řídit přístupová oprávnění pro daný výběr dat.

Zobrazení jsou v mnoha ohledech podobná CTE. V tom smyslu, že svá řešení vyvíjíte modulárním způsobem se zaměřením na jednu jednotku v době od začátku do konce. Také v tom smyslu, že můžete mít více odkazů na název pohledu z vnějšího dotazu. Ale lepší než CTE, pohledy nejsou omezeny pouze na rozsah vnějšího příkazu, ale jsou opakovaně použitelné, dokud nejsou vyřazeny z databáze.

O názorech je toho ještě hodně co říci a v diskusi budu pokračovat příští měsíc. Mezitím vás chci nechat s myšlenkou. S odvozenými tabulkami a CTE můžete argumentovat ve prospěch SELECT * ve vnitřním dotazu. Podrobnosti viz pouzdro, které jsem na to vyrobil v části 3 seriálu. Mohl byste udělat podobný případ s pohledy, nebo je to s nimi špatný nápad?


  1. POSTUP:Spouštění naplánovaných úloh pomocí aplikace Microsoft Access

  2. Uložená procedura volání SQL pro každý řádek bez použití kurzoru

  3. Formátování telefonního čísla v SQL Server (T-SQL)

  4. Název tabulky nebo sloupce nemůže začínat číslicí?