sql >> Database >  >> RDS >> Database

Grondbeginselen van tabeluitdrukkingen, deel 9 – Weergaven, vergeleken met afgeleide tabellen en CTE's

Dit is het 9e deel in een serie over benoemde tabeluitdrukkingen. In deel 1 heb ik de achtergrond gegeven voor benoemde tabeluitdrukkingen, waaronder afgeleide tabellen, gemeenschappelijke tabeluitdrukkingen (CTE's), views en inline table valued-functies (iTVF's). In deel 2, deel 3 en deel 4 heb ik me gericht op afgeleide tabellen. In deel 5, deel 6, deel 7 en deel 8 concentreerde ik me op CTE's. Zoals ik heb uitgelegd, zijn afgeleide tabellen en CTE's statement-scope benoemde tabelexpressies. Zodra de verklaring die ze definieert klaar is, zijn ze weg.

We zijn nu klaar om verder te gaan met het behandelen van herbruikbare benoemde tabelexpressies. Dat wil zeggen, degenen die als een object in de database zijn gemaakt en daar permanent blijven tenzij ze worden verwijderd. Als zodanig zijn ze toegankelijk en herbruikbaar voor iedereen met de juiste machtigingen. Weergaven en iTVF's vallen in deze categorie. Het verschil tussen de twee is vooral dat de eerste geen invoerparameters ondersteunt en de laatste wel.

In dit artikel begin ik met de berichtgeving. Zoals ik eerder deed, zal ik me eerst concentreren op logische of conceptuele aspecten en later overgaan tot optimalisatieaspecten. Met het eerste artikel over views wil ik het licht beginnen, me concentreren op wat een view is, de juiste terminologie gebruiken, en ontwerpoverwegingen van views vergelijken met die van de eerder besproken afgeleide tabellen en CTE's.

In mijn voorbeelden gebruik ik een voorbeelddatabase met de naam TSQLV5. Je kunt het script dat het maakt en vult hier vinden, en het ER-diagram hier.

Wat is een weergave?

Zoals gebruikelijk bij het bespreken van relationele theorie, wordt ons SQL-beoefenaars vaak verteld dat de terminologie die we gebruiken verkeerd is. Dus, in deze geest, zal ik meteen beginnen met te zeggen dat wanneer je de term tabellen en weergaven gebruikt , het is verkeerd. Ik heb dit geleerd van Chris Date.

Bedenk dat een tabel de tegenhanger van SQL is voor een relatie (wat de discussie over waarden en variabelen een beetje versimpelt). Een tabel kan een basistabel zijn die is gedefinieerd als een object in de database, of het kan een tabel zijn die wordt geretourneerd door een expressie, meer specifiek een tabelexpressie. Dat is vergelijkbaar met het feit dat een relatie er een kan zijn die wordt geretourneerd door een relationele expressie. Een tabeluitdrukking kan een query zijn.

Wat is nu een uitzicht? Het is een benoemde tabeluitdrukking, net zoals een CTE een benoemde tabeluitdrukking is. Het is gewoon dat, zoals ik al zei, een weergave een herbruikbare benoemde tabelexpressie is die als een object in de database wordt gemaakt en toegankelijk is voor degenen met de juiste machtigingen. Dit wil allemaal zeggen, een weergave is een tafel. Het is geen basistafel, maar toch een tafel. Dus net zoals zeggen "een rechthoek en een vierkant" of "een whisky en een Lagavulin" vreemd zou lijken (tenzij je teveel Lagavulin had!), is het gebruik van "tabellen en weergaven" net zo ongepast.

Syntaxis

Hier is de T-SQL-syntaxis voor een CREATE VIEW-instructie:

MAAK [ OF WIJZIG ] VIEW [ . ] [ () ]
[ MET ]
AS

[ MET CONTROLE OPTIE ]
[; ]

De instructie CREATE VIEW moet de eerste en enige instructie in de batch zijn.

Merk op dat het CREATE OR ALTER-gedeelte is geïntroduceerd in SQL Server 2016 SP1, dus als u een eerdere versie gebruikt, moet u werken met afzonderlijke CREATE VIEW- en ALTER VIEW-instructies, afhankelijk van of het object al bestaat of niet. Zoals u waarschijnlijk wel weet, behoudt het wijzigen van een bestaand object de toegewezen machtigingen. Dat is een van de redenen waarom het meestal verstandig is om een ​​bestaand object te wijzigen in plaats van het te laten vallen en opnieuw te maken. Wat sommige mensen verrast, is dat het wijzigen van een weergave de bestaande weergavekenmerken niet behoudt; deze moeten opnieuw worden opgegeven als u ze wilt behouden.

Hier is een voorbeeld van een eenvoudige weergavedefinitie die Amerikaanse klanten vertegenwoordigt:

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

En hier is een verklaring die de mening in twijfel trekt:

SELECT custid, companyname
FROM Sales.USACustomers;

Tussen de instructie die de weergave maakt en de instructie die de view opvraagt, vindt u dezelfde drie elementen die betrokken zijn bij een instructie tegen een afgeleide tabel of een CTE:

  1. De innerlijke tabelexpressie (de innerlijke query van de view)
  2. De toegewezen tabelnaam (de weergavenaam)
  3. De instructie met de buitenste query tegen de weergave

Degenen onder u met een scherp oog zullen hebben opgemerkt dat er hier eigenlijk twee tabeluitdrukkingen zijn. Er is de binnenste (de innerlijke vraag van de weergave) en er is de buitenste (de vraag in de verklaring tegen de weergave). In de instructie met de query tegen de view, is de query zelf een tabelexpressie en zodra u de terminator toevoegt, wordt het een instructie. Dit klinkt misschien kieskeurig, maar als je dit begrijpt en de dingen bij hun juiste naam noemt, weerspiegelt het wel je kennis. En is het niet geweldig als je weet dat je het weet?

Ook zijn alle vereisten van de tabelexpressie in afgeleide tabellen en CTE's die we eerder in de serie hebben besproken, van toepassing op de tabelexpressie waarop de weergave is gebaseerd. Ter herinnering, de vereisten zijn:

  • Alle kolommen van de tabelexpressie moeten een naam hebben
  • Alle kolomnamen van de tabelexpressie moeten uniek zijn
  • De rijen van de tabelexpressie hebben geen volgorde

Als u uw begrip van wat er achter deze vereisten zit, wilt opfrissen, raadpleegt u de sectie "Een tabeluitdrukking is een tabel" in deel 2 van de serie. Zorg ervoor dat u vooral het gedeelte "geen bestelling" begrijpt. Ter herinnering:een tabeluitdrukking is een tabel en heeft als zodanig geen volgorde. Daarom kunt u geen weergave maken op basis van een query met een ORDER BY-component, tenzij deze clausule er is om een ​​TOP- of OFFSET-FETCH-filter te ondersteunen. En zelfs met deze uitzondering die toestaat dat de inner query een ORDER BY-component heeft, wil je onthouden dat als de buitenste query tegen de view geen eigen ORDER BY-component heeft, je geen garantie krijgt dat de query zal terugkeren de rijen in een bepaalde volgorde, laat staan ​​het waargenomen gedrag. Dit is super belangrijk om te begrijpen!

Nesten en meerdere referenties

Bij het bespreken van ontwerpoverwegingen van afgeleide tabellen en CTE's, heb ik de twee vergeleken in termen van zowel nesten als meerdere verwijzingen. Laten we nu eens kijken hoe de weergaven het doen in deze afdelingen. Ik begin met nestelen. Voor dit doel vergelijken we code die jaren teruggeeft waarin meer dan 70 klanten bestellingen hebben geplaatst met behulp van afgeleide tabellen, CTE's en weergaven. Je zag de code met afgeleide tabellen en CTE's al eerder in de serie. Dit is de code die de taak afhandelt met behulp van afgeleide tabellen:

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;

Ik heb erop gewezen dat het belangrijkste nadeel dat ik zie bij afgeleide tabellen hier is dat je afgeleide tabeldefinities nest, en dit kan leiden tot complexiteit bij het begrijpen, onderhouden en oplossen van problemen met dergelijke code.

Hier is de code die dezelfde taak afhandelt met behulp van CTE's:

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;

Ik heb erop gewezen dat dit voor mij aanvoelt als een veel duidelijkere code vanwege het gebrek aan nesting. U kunt elke stap in de oplossing van begin tot eind afzonderlijk in zijn eigen eenheid zien, waarbij de logica van de oplossing duidelijk van boven naar beneden stroomt. Daarom zie ik de CTE-optie in dit opzicht als een verbetering ten opzichte van afgeleide tabellen.

Nu naar uitzicht. Onthoud dat een van de belangrijkste voordelen van views herbruikbaarheid is. U kunt ook toegangsrechten beheren. De ontwikkeling van de betrokken eenheden lijkt iets meer op CTE's in die zin dat u uw aandacht van begin tot eind op één eenheid tegelijk kunt richten. Bovendien hebt u de flexibiliteit om te beslissen of u een afzonderlijke weergave per eenheid in de oplossing wilt maken, of misschien slechts één weergave op basis van een query met benoemde tabeluitdrukkingen met een statement-scope.

Je zou voor de eerste gaan wanneer elk van de eenheden herbruikbaar moet zijn. Dit is de code die u in zo'n geval zou gebruiken en drie weergaven zou maken:

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

U kunt elk van de weergaven afzonderlijk opvragen, maar hier is de code die u zou gebruiken om terug te geven wat de oorspronkelijke taak was.

SELECT orderyear, numcusts
FROM Sales.YearlyCustCountsAbove70;

Als er alleen een herbruikbaarheidsvereiste is voor het buitenste deel (wat de oorspronkelijke taak vereiste), is het niet echt nodig om drie verschillende weergaven te ontwikkelen. U kunt één weergave maken op basis van een query met CTE's of afgeleide tabellen. Hier leest u hoe u dat zou doen met een zoekopdracht waarbij CTE's betrokken zijn:

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

Trouwens, als het niet duidelijk was, kunnen de CTE's waarop de innerlijke query van de weergave is gebaseerd recursief zijn.

Laten we verder gaan met gevallen waarin u meerdere verwijzingen naar dezelfde tabelexpressie van de buitenste query nodig hebt. De taak voor dit voorbeeld is om de jaarlijkse ordertelling per jaar te berekenen en de telling in elk jaar te vergelijken met het voorgaande jaar. De eenvoudigste manier om dit te bereiken is door de LAG-vensterfunctie te gebruiken, maar we gebruiken een join tussen twee instanties van een tabeluitdrukking die de jaarlijkse ordertellingen vertegenwoordigt, alleen om een ​​geval met meerdere verwijzingen tussen de drie tools te vergelijken.

Dit is de code die we eerder in de serie hebben gebruikt om de taak met afgeleide tabellen af ​​te handelen:

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;

Er is hier een heel duidelijk nadeel. U moet de definitie van de tabeluitdrukking twee keer herhalen. U definieert in wezen twee benoemde tabeluitdrukkingen op basis van dezelfde querycode.

Hier is de code die dezelfde taak afhandelt met behulp van CTE's:

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;

Er is hier een duidelijk voordeel; u definieert slechts één benoemde tabelexpressie op basis van een enkele instantie van de inner query, en verwijst er twee keer naar vanuit de outer query.

In die zin lijken weergaven meer op CTE's. U definieert slechts één weergave op basis van slechts één kopie van de zoekopdracht, zoals:

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

Maar beter dan met CTE's, bent u niet beperkt tot het hergebruiken van de benoemde tabeluitdrukking alleen in de buitenste instructie. U kunt de weergavenaam zo vaak als u wilt hergebruiken, met een willekeurig aantal niet-gerelateerde zoekopdrachten, zolang u over de juiste machtigingen beschikt. Hier is de code om de taak te bereiken door meerdere verwijzingen naar de weergave te gebruiken:

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;

Het lijkt erop dat weergaven meer lijken op CTE's dan op afgeleide tabellen, met als extra functionaliteit een meer herbruikbare tool te zijn, met de mogelijkheid om machtigingen te beheren. Of om het om te draaien:het is waarschijnlijk gepast om een ​​CTE te zien als een weergave met een statement-scope. Wat nu echt geweldig zou zijn, is als we ook een benoemde tabeluitdrukking hadden met een groter bereik dan dat van een CTE, enger dan dat van een weergave. Zou het bijvoorbeeld niet geweldig zijn geweest als we een benoemde tabeluitdrukking op sessieniveau hadden gehad?

Samenvatting

Ik hou van dit onderwerp. Er zit zoveel in tabeluitdrukkingen dat geworteld is in relationele theorie, die op zijn beurt geworteld is in wiskunde. Ik vind het heerlijk om te weten wat de juiste termen voor dingen zijn, en er over het algemeen voor te zorgen dat ik de basis goed heb uitgedacht, ook al lijkt het voor sommigen misschien kieskeurig en overdreven pedant. Als ik terugkijk op mijn leerproces door de jaren heen, zie ik een heel duidelijk pad tussen aandringen op een goed begrip van de fundamenten, het gebruik van de juiste terminologie, en later echt je spullen kennen als het om de veel geavanceerdere en complexere dingen gaat.

Dus, wat zijn de kritische stukken als het gaat om weergaven?

  • Een weergave is een tabel.
  • Het is een tabel die is afgeleid van een query (een tabeluitdrukking).
  • Het krijgt een naam die voor de gebruiker lijkt op een tabelnaam, aangezien het een tabelnaam is.
  • Het wordt gemaakt als een permanent object in de database.
  • Je kunt toegangsrechten voor de weergave beheren.

Weergaven zijn in een aantal opzichten vergelijkbaar met CTE's. In die zin dat u uw oplossingen modulair ontwikkelt, waarbij u zich van begin tot eind concentreert op één unit tegelijk. Ook in die zin dat u meerdere verwijzingen naar de weergavenaam kunt hebben vanuit de buitenste query. Maar beter dan CTE's, zijn views niet alleen beperkt tot het bereik van de buitenste verklaring, maar zijn ze herbruikbaar totdat ze uit de database worden verwijderd.

Er valt nog veel meer te zeggen over meningen en ik zal de discussie volgende maand voortzetten. In de tussentijd wil ik je achterlaten met een gedachte. Met afgeleide tabellen en CTE's zou je in een inner query kunnen pleiten voor SELECT *. Zie de case die ik ervoor heb gemaakt in deel 3 van de serie voor details. Zou je een soortgelijk geval kunnen maken met views, of is het een slecht idee daarmee?


  1. Hoe ELT() werkt in MariaDB

  2. SQL Server sp_msforeachable gebruik om alleen die tabellen te selecteren die aan een bepaalde voorwaarde voldoen

  3. SQLite Join

  4. Controleer of een object een tabel, weergave of opgeslagen procedure is in SQL Server met behulp van de OBJECTPROPERTY()-functie