Dette er 9. del i en serie om navngivne tabeludtryk. I del 1 gav jeg baggrunden for navngivne tabeludtryk, som inkluderer afledte tabeller, almindelige tabeludtryk (CTE'er), visninger og inline-tabelværdierede funktioner (iTVF'er). I del 2, del 3 og del 4 fokuserede jeg på afledte tabeller. I del 5, del 6, del 7 og del 8 fokuserede jeg på CTE'er. Som jeg forklarede, er afledte tabeller og CTE'er sætningsomfangede navngivne tabeludtryk. Når erklæringen, der definerer dem, er færdig, er de væk.
Vi er nu klar til at gå videre til dækning af genbrugelige navngivne tabeludtryk. Det vil sige dem, der er oprettet som et objekt i databasen, og forbliver der permanent, medmindre de slettes. Som sådan er de tilgængelige og genbrugelige for alle, der har de rigtige tilladelser. Visninger og iTVF'er falder ind under denne kategori. Forskellen mellem de to er primært, at førstnævnte ikke understøtter inputparametre, og sidstnævnte gør.
I denne artikel starter jeg dækningen af synspunkter. Som jeg gjorde før, vil jeg først fokusere på logiske eller konceptuelle aspekter og på et senere tidspunkt gå videre til optimeringsaspekter. Med den første artikel om synspunkter vil jeg starte lyset, fokusere på hvad et synspunkt er, ved at bruge korrekt terminologi og sammenligne designovervejelser for synspunkter med de tidligere diskuterede afledte tabeller og CTE'er.
I mine eksempler vil jeg bruge en prøvedatabase kaldet TSQLV5. Du kan finde scriptet, der opretter og udfylder det her, og dets ER-diagram her.
Hvad er en udsigt?
Som sædvanligt, når vi diskuterer relationsteori, får vi SQL-udøvere ofte at vide, at den terminologi, vi bruger, er forkert. Så i denne ånd vil jeg starte med at sige, at når du bruger udtrykket tabeller og synspunkter , det er forkert. Det har jeg lært af Chris Date.
Husk at en tabel er SQLs modstykke til en relation (oversimplificerer diskussionen omkring værdier og variabler lidt). En tabel kan være en basistabel, der er defineret som et objekt i databasen, eller det kan være en tabel, der returneres af et udtryk – mere specifikt et tabeludtryk. Det svarer til det faktum, at en relation kunne være en, der er returneret fra et relationelt udtryk. Et tabeludtryk kunne være en forespørgsel.
Hvad er nu en udsigt? Det er et navngivet tabeludtryk, ligesom en CTE er et navngivet tabeludtryk. Det er bare, som jeg sagde, en visning er et genbrugeligt navngivet tabeludtryk, der er oprettet som et objekt i databasen, og er tilgængeligt for dem, der har de rigtige tilladelser. Dette er alt at sige, en udsigt er et bord. Det er ikke et basisbord, men et bord ikke desto mindre. Så ligesom at sige "et rektangel og en firkant" eller "en whisky og en Lagavulin" ville virke mærkeligt (medmindre du havde for meget Lagavulin!), er det lige så upassende at bruge "tabeller og udsigter".
Syntaks
Her er T-SQL-syntaksen for en CREATE VIEW-sætning:
OPRET [ELLER ÆNDRING] VIS [[ MED
AS
[ MED KONTROLMULIGHED ]
[; ]
CREATE VIEW-sætningen skal være den første og eneste sætning i batchen.
Bemærk, at CREATE OR ALTER-delen blev introduceret i SQL Server 2016 SP1, så hvis du bruger en tidligere version, skal du arbejde med separate CREATE VIEW- og ALTER VIEW-sætninger, afhængigt af om objektet allerede eksisterer eller ej. Som du sikkert godt ved, bevarer ændring af et eksisterende objekt tildelte tilladelser. Det er en af grundene til, at det normalt er fornuftigt at ændre et eksisterende objekt i modsætning til at slippe og genskabe det. Det, der overrasker nogle mennesker, er, at ændring af en visning ikke bevarer de eksisterende visningsattributter; disse skal specificeres igen, hvis du vil beholde dem.
Her er et eksempel på en simpel visningsdefinition, der repræsenterer kunder i USA:
BRUG TSQLV5;GO OPRET ELLER ÆNDRING VIS Sales.USACustomersAS SELECT custid, virksomhedsnavn FRA Sales.Customers WHERE country =N'USA';GO
Og her er en erklæring, der stiller spørgsmålstegn ved udsigten:
SELECT custid, companynameFROM Sales.USACustomers;
Mellem sætningen, der skaber visningen, og sætningen, der spørger til den, vil du finde de samme tre elementer, som er involveret i en sætning mod en afledt tabel eller en CTE:
- Det indre tabeludtryk (visningens indre forespørgsel)
- Det tildelte tabelnavn (visningsnavnet)
- Sætningen med den ydre forespørgsel mod visningen
De af jer med et skarpt øje vil have bemærket, at der faktisk er to tabeludtryk involveret her. Der er den indre (udsigtens indre forespørgsel), og der er den ydre (forespørgslen i sætningen mod udsigten). I sætningen med forespørgslen mod visningen, er selve forespørgslen et tabeludtryk, og når du tilføjer terminatoren, bliver det til et udsagn. Det lyder måske kræsent, men hvis du forstår dette og kalder tingene ved deres rigtige navne, afspejler det din viden. Og er det ikke fantastisk, når man ved, at man ved det?
Alle kravene fra tabeludtrykket i afledte tabeller og CTE'er, som vi diskuterede tidligere i serien, gælder også for det tabeludtryk, som visningen er baseret på. Som en påmindelse er kravene:
- Alle tabeludtrykkets kolonner skal have navne
- Alle tabeludtrykkets kolonnenavne skal være unikke
- Tabeludtrykkets rækker har ingen rækkefølge
Hvis du har brug for at genopfriske din forståelse af, hvad der ligger bag disse krav, se afsnittet "Et tabeludtryk er en tabel" i del 2 af serien. Sørg for, at du især forstår "ingen ordre"-delen. Som en kort påmindelse er et tabeludtryk en tabel, og har som sådan ingen orden. Det er derfor, du ikke kan oprette en visning baseret på en forespørgsel med en ORDER BY-klausul, medmindre denne klausul er der for at understøtte et TOP- eller OFFSET-FETCH-filter. Og selv med denne undtagelse, der tillader den indre forespørgsel at have en ORDER BY-klausul, vil du huske, at hvis den ydre forespørgsel mod visningen ikke har sin egen ORDER BY-klausul, får du ikke en garanti for, at forespørgslen vender tilbage rækkerne i en bestemt rækkefølge, pyt med den observerede adfærd. Dette er super vigtigt at forstå!
Indlejring og flere referencer
Da jeg diskuterede designovervejelser af afledte tabeller og CTE'er, sammenlignede jeg de to med hensyn til både indlejring og flere referencer. Lad os nu se, hvordan visninger klarer sig i disse afdelinger. Jeg starter med rede. Til dette formål vil vi sammenligne kode, der returnerer år, hvor mere end 70 kunder afgav ordrer ved hjælp af afledte tabeller, CTE'er og visninger. Du har allerede set koden med afledte tabeller og CTE'er tidligere i serien. Her er koden, der håndterer opgaven ved hjælp af afledte tabeller:
SELECT orderyear, numcustsFROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM (SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2WHERE numcusts> 70;
Jeg påpegede, at den største ulempe, som jeg ser med afledte tabeller her, er det faktum, at du indlejrer afledte tabeldefinitioner, og dette kan føre til kompleksitet i at forstå, vedligeholde og fejlfinde sådan kode.
Her er koden, der håndterer den samme opgave ved hjælp af CTE'er:
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, numcustsFROM C2WHERE numcusts> 70;
Jeg påpegede, at for mig føles dette som meget klarere kode på grund af manglen på indlejring. Du kan se hvert trin i løsningen fra start til slut separat i sin egen enhed, hvor løsningens logik flyder klart fra top til bund. Derfor ser jeg CTE-muligheden som en forbedring i forhold til afledte tabeller i denne henseende.
Nu til visninger. Husk, at en af de vigtigste fordele ved visninger er genbrugelighed. Du kan også kontrollere adgangstilladelser. Udviklingen af de involverede enheder minder lidt mere om CTE'er i den forstand, at du kan fokusere din opmærksomhed på én enhed ad gangen fra start til slut. Desuden har du fleksibiliteten til at beslutte, om du vil oprette en separat visning pr. enhed i løsningen, eller måske kun én visning baseret på en forespørgsel, der involverer navngivne tabeludtryk med sætningsomfang.
Du ville gå med førstnævnte, når hver af enhederne skal genbruges. Her er koden, du ville bruge i et sådant tilfælde, hvilket skaber tre visninger:
-- Sales.OrdreÅrCREATE OR ALTER VIEW Sales.OrderYearsAS SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders;GO -- Sales.YearlyCustCountsCREATE OR ALTER VIEW Sales.YearlyCustCountsASCOUNTcusttidsSELECT bestillingsår,CTcust. FRA Salg.OrdreÅr GRUPPER EFTER ordreår;GO -- Sales.Årlige KundeTællerMin70OPRET ELLER ÆNDRING VIS Salg.Årlige KundeTællerAbove70AS VÆLG ordreår, numcust FRA Salg.Årlige KundeTæller WHERE numcusts> 70;Du kan forespørge hver af visningerne uafhængigt, men her er koden, du ville bruge til at returnere, hvad den oprindelige opgave var ude efter.
VÆLG ordreår, numcustsFROM Sales.YearlyCustCountsAbove70;Hvis der kun er krav om genanvendelighed for den yderste del (hvad den oprindelige opgave krævede), er der ikke noget reelt behov for at udvikle tre forskellige visninger. Du kan oprette én visning baseret på en forespørgsel, der involverer CTE'er eller afledte tabeller. Sådan ville du gøre det med en forespørgsel, der involverer CTE'er:
OPRET ELLER ÆNDRING VIS Salg.Årlig KundeTællerAbove70AS MED C1 AS (VÆLG ÅR(ordredato) SOM ordreår, depot FRA Salg.Ordrer ), C2 AS (VÆLG ordreår, COUNT(DISTINCT depot) SOM numcusts FRA C1 GROUP SELECT BY orderyear , numcusts FRA C2 WHERE numcusts> 70;GOForresten, hvis det ikke var indlysende, kan de CTE'er, som visningens indre forespørgsel er baseret på, være rekursive.
Lad os gå videre til tilfælde, hvor du har brug for flere referencer til det samme tabeludtryk fra den ydre forespørgsel. Opgaven for dette eksempel er at beregne det årlige ordretal pr. år og sammenligne antallet i hvert år med det foregående år. Den nemmeste måde at opnå dette på er faktisk at bruge LAG-vinduefunktionen, men vi bruger en sammenføjning mellem to forekomster af et tabeludtryk, der repræsenterer årlige rækkefølgen, blot for at sammenligne en multireferencesag blandt de tre værktøjer.
Dette er koden, som vi brugte tidligere i serien til at håndtere opgaven med afledte tabeller:
VÆLG CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diffFROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numbers FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS CUR LEFT YDRE JOIN ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numbers FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS PRV ON CUR.orderyear =PRV.orderyear + 1;Der er en meget klar ulempe her. Du skal gentage definitionen af tabeludtrykket to gange. Du definerer i det væsentlige to navngivne tabeludtryk baseret på den samme forespørgselskode.
Her er koden, der håndterer den samme opgave ved hjælp af CTE'er:
WITH OrdCount AS( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numbers FROM Sales.Orders GROUP BY YEAR(orderdate))SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diffFROM OrdCount AS CUR VENSTRE YDRE JOIN OrdCount AS PRV PÅ CUR.orderyear =PRV.orderyear + 1;Der er en klar fordel her; du definerer kun ét navngivet tabeludtryk baseret på en enkelt forekomst af den indre forespørgsel og henviser til den to gange fra den ydre forespørgsel.
Visninger minder mere om CTE'er i denne forstand. Du definerer kun én visning baseret på kun én kopi af forespørgslen, som sådan:
OPRET ELLER ÆNDRING VIS Salg.Årlige ordretællerSOM SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numbers FROM Sales.Orders GROUP BY YEAR(orderdate);GOMen bedre end med CTE'er er du ikke begrænset til kun at genbruge det navngivne tabeludtryk i den ydre sætning. Du kan genbruge visningsnavnet et vilkårligt antal gange, du vil, med et hvilket som helst antal ikke-relaterede forespørgsler, så længe du har de rigtige tilladelser. Her er koden til at udføre opgaven ved at bruge flere referencer til visningen:
VÆLG CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diffFROM Sales.YearlyOrderCounts AS CUR LEFT YDRE JOIN Sales.YearlyOrderCounts AS PRV ON CUR.orderyear =PRV.orderyear + 1;Det ser ud til, at visninger ligner mere CTE'er end afledte tabeller, med den ekstra funktionalitet at være et mere genbrugeligt værktøj med mulighed for at kontrollere tilladelser. Eller for at vende det om, så er det nok passende at tænke på en CTE som et statement-scoped view. Det, der kunne være virkelig vidunderligt, er, hvis vi også havde et navngivet bordudtryk med et bredere omfang end en CTE, snævrere end et udsyn. Ville det for eksempel ikke have været fantastisk, hvis vi havde et sessionsniveau-omfanget navngivet tabeludtryk?
Oversigt
Jeg elsker dette emne. Der er så meget i tabeludtryk, der er forankret i relationsteori, som igen er forankret i matematik. Jeg elsker at vide, hvad de rigtige udtryk for ting er, og generelt at sørge for, at jeg har fundet grundlaget omhyggeligt, selvom det for nogle kan virke som at være kræsen og for pedantisk. Når jeg ser tilbage på min læreproces gennem årene, kan jeg se en meget klar vej mellem at insistere på et godt greb om grundlaget, bruge korrekt terminologi og virkelig at kende dine ting senere, når det kommer til de langt mere avancerede og komplekse ting.
Så hvad er de kritiske stykker, når det kommer til synspunkter?
- En visning er en tabel.
- Det er en tabel, der er afledt af en forespørgsel (et tabeludtryk).
- Den får et navn, der for brugeren ser ud som et tabelnavn, da det er et tabelnavn.
- Det er oprettet som et permanent objekt i databasen.
- Du kan kontrollere adgangstilladelser i forhold til visningen.
Visninger ligner CTE'er på flere måder. I den forstand, at du udvikler dine løsninger på en modulær måde med fokus på én enhed ad gangen fra start til slut. Også i den forstand, at du kan have flere referencer til visningsnavnet fra den ydre forespørgsel. Men bedre end CTE'er er visninger ikke kun begrænset til den ydre erklærings omfang, de kan snarere genbruges, indtil de slettes fra databasen.
Der er meget mere at sige om synspunkter, og jeg fortsætter diskussionen i næste måned. I mellemtiden vil jeg efterlade dig med en tanke. Med afledte tabeller og CTE'er kan du lave en sag til fordel for SELECT * i en indre forespørgsel. Se den sag, jeg lavede til den i del 3 i serien for detaljer. Kunne du lave en lignende sag med synspunkter, eller er det en dårlig idé med dem?