sql >> Databasteknik >  >> RDS >> Database

Grundläggande tabelluttryck, del 9 – vyer, jämfört med härledda tabeller och CTE

Detta är den nionde delen i en serie om namngivna tabelluttryck. I del 1 gav jag bakgrunden till namngivna tabelluttryck, som inkluderar härledda tabeller, vanliga tabelluttryck (CTE), vyer och inline-tabellvärderade funktioner (iTVF). I del 2, del 3 och del 4 fokuserade jag på härledda tabeller. I del 5, del 6, del 7 och del 8 fokuserade jag på CTE. Som jag förklarade är härledda tabeller och CTE:er namngivna tabelluttryck med satsomfattning. När uttalandet som definierar dem är klart, är de borta.

Vi är nu redo att fortsätta till täckning av återanvändbara namngivna tabelluttryck. Det vill säga sådana som skapas som ett objekt i databasen och stannar där permanent om de inte tas bort. Som sådana är de tillgängliga och återanvändbara för alla som har rätt behörigheter. Visningar och iTVFs faller inom denna kategori. Skillnaden mellan de två är främst att den förra inte stöder inmatningsparametrar och den senare gör det.

I den här artikeln börjar jag bevakningen av åsikter. Som jag gjorde tidigare kommer jag först att fokusera på logiska eller konceptuella aspekter och vid en senare tidpunkt gå vidare till optimeringsaspekter. Med den första artikeln om vyer vill jag börja med att fokusera på vad en vy är, använda korrekt terminologi och jämföra designöverväganden för vyer med de i de tidigare diskuterade härledda tabellerna och CTE:erna.

I mina exempel kommer jag att använda en exempeldatabas som heter TSQLV5. Du kan hitta skriptet som skapar och fyller det här, och dess ER-diagram här.

Vad är en vy?

Som vanligt när vi diskuterar relationsteori får vi SQL-utövare ofta höra att terminologin vi använder är felaktig. Så, i denna anda, direkt, jag börjar med att säga att när du använder termen tabeller och vyer , det är fel. Jag har lärt mig detta av Chris Date.

Kom ihåg att en tabell är SQLs motsvarighet till en relation (överförenkla diskussionen kring värden och variabler lite). En tabell kan vara en bastabell definierad som ett objekt i databasen, eller det kan vara en tabell som returneras av ett uttryck – mer specifikt ett tabelluttryck. Det liknar det faktum att en relation kan vara en som återkommer från ett relationsuttryck. Ett tabelluttryck kan vara en fråga.

Nu, vad är en vy? Det är ett namngivet tabelluttryck, ungefär som en CTE är ett namngivet tabelluttryck. Det är bara som jag sa, en vy är ett återanvändbart namngivet tabelluttryck som skapas som ett objekt i databasen och är tillgängligt för de som har rätt behörigheter. Detta är allt att säga, en vy är ett bord. Det är inte ett basbord, men ett bord ändå. Så precis som att säga "en rektangel och en fyrkant" eller "en whisky och en Lagavulin" skulle verka konstigt (om du inte hade för mycket Lagavulin!), är det lika olämpligt att använda "tabeller och vyer".

Syntax

Här är T-SQL-syntaxen för en CREATE VIEW-sats:

SKAPA [ ELLER ÄNDRA ] VISA [ . ] [ () ]
[ MED ]
AS

[ MED KONTROLLVAL ]
[; ]

CREATE VIEW-satsen måste vara den första och enda satsen i batchen.

Observera att CREATE OR ALTER-delen introducerades i SQL Server 2016 SP1, så om du använder en tidigare version måste du arbeta med separata CREATE VIEW- och ALTER VIEW-satser beroende på om objektet redan finns eller inte. Som du säkert vet behåller du tilldelade behörigheter om du ändrar ett befintligt objekt. Det är en av anledningarna till att det vanligtvis är klokt att ändra ett befintligt objekt i stället för att släppa och återskapa det. Det som överraskar vissa människor är att ändringar av en vy inte behåller de befintliga vyattributen; dessa måste specificeras om du vill behålla dem.

Här är ett exempel på en enkel vydefinition som representerar kunder i USA:

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

Och här är ett uttalande som ifrågasätter utsikten:

SELECT custid, companyname
FROM Sales.USACustomers;

Mellan satsen som skapar vyn och satsen som frågar efter den, hittar du samma tre element som är involverade i en sats mot en härledd tabell eller en CTE:

  1. Det inre tabelluttrycket (vyns inre fråga)
  2. Det tilldelade tabellnamnet (vynamnet)
  3. Satsen med den yttre frågan mot vyn

De av er med ett skarpt öga kommer att ha lagt märke till att det faktiskt finns två tabelluttryck inblandade här. Det finns den inre (vyns inre fråga), och det finns den yttre (frågan i uttalandet mot vyn). I satsen med frågan mot vyn är själva frågan ett tabelluttryck, och när du väl lägger till terminatorn blir det en sats. Det här kanske låter kräsen, men om du förstår det här och kallar saker och ting vid deras rätta namn, reflekterar det på din kunskap. Och är det inte bra när du vet att du vet?

Alla krav från tabelluttrycket i härledda tabeller och CTE:er som vi diskuterade tidigare i serien gäller också för det tabelluttryck som vyn är baserad på. Som en påminnelse är kraven:

  • Alla tabelluttryckets kolumner måste ha namn
  • Alla tabelluttryckets kolumnnamn måste vara unika
  • Tabelluttryckets rader har ingen ordning

Om du behöver förnya din förståelse för vad som ligger bakom dessa krav, se avsnittet "Ett tabelluttryck är en tabell" i del 2 av serien. Se till att du förstår "ingen ordning"-delen. Som en kort påminnelse är ett tabelluttryck en tabell och har som sådan ingen ordning. Det är därför du inte kan skapa en vy baserad på en fråga med en ORDER BY-sats, om inte denna sats är till för att stödja ett TOP- eller OFFSET-FETCH-filter. Och även med detta undantag som tillåter den inre frågan att ha en ORDER BY-sats, vill du komma ihåg att om den yttre frågan mot vyn inte har en egen ORDER BY-sats, får du ingen garanti för att frågan kommer att returneras raderna i någon speciell ordning, strunt i det observerade beteendet. Detta är superviktigt att förstå!

Inkapsling och flera referenser

När jag diskuterade designöverväganden för härledda tabeller och CTE:er jämförde jag de två när det gäller både kapsling och flera referenser. Låt oss nu se hur visningar klarar sig på dessa avdelningar. Jag börjar med häckning. För detta ändamål kommer vi att jämföra kod som returnerar år då mer än 70 kunder gjorde beställningar med hjälp av härledda tabeller, CTE:er och vyer. Du såg redan koden med härledda tabeller och CTE tidigare i serien. Här är koden som hanterar uppgiften med hjälp av härledda tabeller:

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;

Jag påpekade att den största nackdelen som jag ser med härledda tabeller här är det faktum att du kapslar härledda tabelldefinitioner, och detta kan leda till komplexitet i att förstå, underhålla och felsöka sådan kod.

Här är koden som hanterar samma uppgift med hjälp av 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;

Jag påpekade att för mig känns detta som mycket tydligare kod på grund av bristen på kapsling. Du kan se varje steg i lösningen från början till slut separat i sin egen enhet, där lösningens logik flyter tydligt uppifrån och ner. Därför ser jag CTE-alternativet som en förbättring jämfört med härledda tabeller i detta avseende.

Nu till vyer. Kom ihåg att en av de främsta fördelarna med vyer är återanvändbarhet. Du kan också kontrollera åtkomstbehörigheter. Utvecklingen av de inblandade enheterna är lite mer lik CTE i den meningen att du kan fokusera din uppmärksamhet på en enhet i taget från början till slut. Dessutom har du flexibiliteten att bestämma om du vill skapa en separat vy per enhet i lösningen, eller kanske bara en vy baserad på en fråga som involverar namngivna tabelluttryck med satsomfattning.

Du skulle gå med den förra när var och en av enheterna måste kunna återanvändas. Här är koden du skulle använda i ett sådant fall och skapa tre vyer:

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

Du kan fråga var och en av vyerna oberoende, men här är koden du skulle använda för att returnera vad den ursprungliga uppgiften var ute efter.

SELECT orderyear, numcusts
FROM Sales.YearlyCustCountsAbove70;

Om det finns ett återanvändningskrav endast för den yttersta delen (vad den ursprungliga uppgiften krävde), finns det inget verkligt behov av att utveckla tre olika vyer. Du kan skapa en vy baserat på en fråga som involverar CTE:er eller härledda tabeller. Så här skulle du göra det med en fråga som involverar 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

Förresten, om det inte var uppenbart kan de CTE:er som vyns inre fråga baseras på vara rekursiva.

Låt oss gå vidare till fall där du behöver flera referenser till samma tabelluttryck från den yttre frågan. Uppgiften för detta exempel är att beräkna det årliga ordertalet per år och jämföra antalet varje år med föregående år. Det enklaste sättet att uppnå detta är faktiskt att använda LAG-fönsterfunktionen, men vi kommer att använda en koppling mellan två instanser av ett tabelluttryck som representerar årliga ordningstal bara för att jämföra ett multireferensfall bland de tre verktygen.

Det här är koden som vi använde tidigare i serien för att hantera uppgiften med härledda tabeller:

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;

Det finns en mycket tydlig nackdel här. Du måste upprepa definitionen av tabelluttrycket två gånger. Du definierar i huvudsak två namngivna tabelluttryck baserat på samma frågekod.

Här är koden som hanterar samma uppgift med hjälp av 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;

Det finns en klar fördel här; du definierar bara ett namngivet tabelluttryck baserat på en enda instans av den inre frågan och hänvisar till den två gånger från den yttre frågan.

Vyerna liknar mer CTE i denna mening. Du definierar bara en vy baserat på endast en kopia av frågan, som så:

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

Men bättre än med CTE:er är du inte begränsad till att återanvända det namngivna tabelluttrycket endast i det yttre uttalandet. Du kan återanvända vynamnet hur många gånger du vill, med valfritt antal orelaterade frågor, så länge du har rätt behörigheter. Här är koden för att utföra uppgiften genom att använda flera referenser till vyn:

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;

Det verkar som om vyer mer liknar CTE än härledda tabeller, med den extra funktionaliteten att vara ett mer återanvändbart verktyg, med möjlighet att kontrollera behörigheter. Eller för att vända på det, det är förmodligen lämpligt att tänka på en CTE som en uttalad syn. Vad som nu skulle kunna vara riktigt underbart är om vi också hade ett namngivet tabelluttryck med en bredare räckvidd än en CTE, smalare än en vy. Skulle det till exempel inte ha varit bra om vi hade ett tabelluttryck med namn på sessionsnivå?

Sammanfattning

Jag älskar det här ämnet. Det finns så mycket i tabelluttryck som är förankrat i relationsteori, som i sin tur är förankrat i matematik. Jag älskar att veta vad de rätta termerna för saker är, och generellt sett se till att jag har grunderna noggrant utredda, även om det för vissa kan verka som att vara kräsen och överpedant. När jag ser tillbaka på min inlärningsprocess genom åren kan jag se en mycket tydlig väg mellan att insistera på ett bra grepp om grunderna, att använda korrekt terminologi och att verkligen kunna dina saker senare när det kommer till mycket mer avancerade och komplexa saker.

Så, vilka är de kritiska bitarna när det kommer till åsikter?

  • En vy är en tabell.
  • Det är en tabell som härleds från en fråga (ett tabelluttryck).
  • Den får ett namn som för användaren ser ut som ett tabellnamn, eftersom det är ett tabellnamn.
  • Det skapas som ett permanent objekt i databasen.
  • Du kan styra åtkomstbehörigheter mot vyn.

Åsikter liknar CTE på ett antal sätt. I den meningen att du utvecklar dina lösningar på ett modulärt sätt, med fokus på en enhet i taget från början till slut. Även i den meningen att du kan ha flera referenser till vynamnet från den yttre frågan. Men bättre än CTE:er är åsikter inte begränsade bara till det yttre uttalandets omfattning, utan är snarare återanvändbara tills de tas bort från databasen.

Det finns mycket mer att säga om åsikter, och jag fortsätter diskussionen nästa månad. Under tiden vill jag lämna dig med en tanke. Med härledda tabeller och CTE:er kan du göra ett fall till förmån för SELECT * i en inre fråga. Se fallet jag gjorde för det i del 3 i serien för detaljer. Kan du göra ett liknande fall med synpunkter, eller är det en dålig idé med dessa?


  1. MySQL paginering utan dubbelfråga?

  2. Grunderna i SQL Server ALTER TABLE Statement

  3. Hur man skapar en oracle sql script spool fil

  4. Beräkna löpande total / löpande balans