Første gang Karl hørte om SQL Server CTE var, da han ledte efter noget, der kunne gøre hans SQL-kode lettere for øjet. Det er lidt hovedpine, når man ser på det. Anton, hans bekymrede kollega, spurgte ham om CTE. Karl troede, at Anton hentydede til sin hovedpine. Måske hørte han det hele forkert, så han svarede:"Selvfølgelig ikke." Det sjove er, at han henviste til kronisk traumatisk encefalopati, også en CTE - en neurodegenerativ sygdom forårsaget af gentagne hovedskader. Men baseret på Karls svar vidste Anton med sikkerhed, at hans kollega var uvidende om, hvad han sagde.
Hvilken skør måde at introducere CTE'er på! Så før du går ind i samme båd, lad os præcisere, hvad er SQL CTE eller almindelige tabeludtryk i SQL-verdenen?
Du kan læse det grundlæggende her. I mellemtiden lærer vi lidt mere om, hvad der skete i denne usædvanlige historie.
4 grundlæggende ting om CTE i SQL Server
“En SQL CTE har et navn”
Anton startede med ideen om, at SQL CTE'er midlertidigt kaldes resultatsæt. Da det er midlertidigt, er CTE begrænset i omfang.
"Så, det er ligesom en underforespørgsel?" spurgte Karl.
"På en måde, ja. Men du kan ikke navngive en underforespørgsel," sagde Anton. "En CTE har et navn, der ligner et bord med et navn. Men i stedet for CREATE, bruger du WITH til at skabe det." Derefter skrev han syntaksen på papir:
MED ()AS()
"CTE's Gone When the SELECT er færdig"
Anton fortsatte med at forklare omfanget af SQL CTE.
"En midlertidig tabel kan eksistere inden for procedurens omfang eller globalt. Men CTE er væk, når SELECT er færdig," sagde han med et rim. "Det samme, hvis du bruger det til INSERT, UPDATE eller DELETE," fortsatte han.
"Du kan ikke genbruge det"
"I modsætning til en visning eller en midlertidig tabel kan du ikke genbruge SQL CTE. Navnet er der, så du kan henvise til det i den indre og den ydre forespørgsel. Men det er alt,” fortalte Anton.
"Så hvad er det store ved SQL CTE'er?" spurgte Karl.
"Du kan gøre din kode mere læsbar"
"Den store sag?" Anton besvarede spørgsmålet. "Det er, at du kan gøre din kode let læselig. Er det ikke det, du leder efter?”
"Det er rigtigt," indrømmede Karl.
Så hvad er det næste logiske skridt for Karl at gøre?
Ekstra ting om CTE i SQL
Næste dag fortsatte Karl sin søgen efter SQL CTE. Bortset fra ovenstående, her er hvad han fandt:
- SQL CTE kan være ikke-rekursiv eller rekursiv.
- Ikke kun SQL Server, men også MySQL og Oracle understøtter ideen. Det er faktisk en del af SQL-99-specifikationerne.
- Mens det bruges til at forenkle SQL-kode, forbedrer det ikke ydeevnen.
- Og det erstatter heller ikke underforespørgsler og midlertidige tabeller. Hver har sin plads og anvendelse.
Kort sagt, det er en anden måde at udtrykke en forespørgsel på .
Men Karl var sulten efter flere detaljer, så han fortsatte med at lede efter, hvad der ville fungere, hvad der ikke ville, og hvordan det ville fungere i forhold til underforespørgsler og midlertidige tabeller.
Hvad vil fungere i SQL Server CTE?
Karl gravede videre for at afdække mere om CTE, og listede nedenfor, hvad SQL Server ville acceptere. Tag også et kig på hans studier.
Tildel inline eller eksterne kolonnealiaser
SQL CTE'er understøtter to former for tildeling af kolonnealiaser. Den første er den indlejrede form, som eksemplet nedenfor:
-- Brug et inline kolonnealiasUSE AdventureWorksGO;WITH Sales_CTEAS ( SELECT SalesPersonID, COUNT(*) AS NumberOfOrders FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL GROUP BY SalesPersonID )SELECT a.SalesPersonFerson.
Ovenstående kode bruger kolonnealias i CTE-definitionen, når den bliver tildelt i SELECT-sætningen. Har du bemærket COUNT(*) AS NumberOfOrders ? Det er den indlejrede formular.
Et andet eksempel er den eksterne form:
-- Brug et eksternt kolonnealiasUSE AdventureWorksGO;WITH Sales_CTE(SalesPersonID, NumberOfOrders) AS ( SELECT SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL GROUP BY SalesPersonID,SalesPSELECTerson aSELECTerson ) .NumberOfOrdersFROM Sales_CTE a
Kolonnerne kan også defineres i parentes efter indstilling af CTE-navnet. Bemærk WITH Sales_CTE (SalesPersonID, NumberOfOrders) .
CTE i SQL går forud for en SELECT, INSERT, UPDATE eller DELETE
Dette næste punkt handler om at forbruge CTE. Det første og almindelige eksempel er, når det går forud for en SELECT-sætning.
-- List alle sælgere med deres samlede antal ordrerUSE AdventureWorksGO;WITH Sales_CTE (SalesPersonID, NumberOfOrders) AS (SELECT SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULLersonIDBY )SELECT a.SalesPersonID,CONCAT(P.LastName,', ',P.FirstName,' ',P.MiddleName) AS SalesPerson,a.NumberOfOrdersFROM Sales_CTE aINNER JOIN Person.Person p ON a.SalesPersonID =p.BusinessEntity kode>
Hvad viser dette eksempel?
- Salg_CTE – navnet på CTE.
- (SalesPersonID, NumberOfOrders) – definitionen af CTE-kolonner.
- VÆLG SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader HVOR SalesPersonID IKKE ER NULL GRUPPER EFTER SalesPersonID – det indre SELECT, der definerer CTE.
- VÆLG et.Sælger-ID, CONCAT(P.Efternavn,', ',P.Fornavn,' ',P.MiddleName) SOM sælger – den ydre forespørgsel, der bruger CTE. Dette eksempel bruger en SELECT til at forbruge CTE.
- FRA Sales_CTE a – referencen for den ydre forespørgsel til CTE.
Udover et SELECT fungerer det også med INSERT, UPDATE og DELETE. Her er et eksempel på brug af INSERT:
-- tilføj en stigning på 10 % til medarbejder 16 efter 1 år fra den foregående stigning.USE AdventureWorksGO;WITH LatestEmployeePayAS( SELECT TOP 1 eph.BusinessEntityID ,eph.RateChangeDate ,eph.Rate ,eph.PayFrequency FROMResourcesquency .EmployeePayHistory eph WHERE eph.BusinessEntityID =16 BESTIL EFTER eph.RateChangeDate DESC)INSERT INTO HumanResources.EmployeePayHistorySELECT BusinessEntityID,DATEADD(d,365,RateChangeFrenchedDate,.mE>
I ovenstående liste henter CTE den seneste løn for medarbejder 16. Resultatsættet af CTE bruges derefter til at indsætte en ny rekord i EmployeePayHistory . Karl registrerede sine resultater elegant. Han brugte også passende eksempler.
Definer flere CTE'er i 1 forespørgsel
Det er rigtigt. Karl fandt ud af, at flere CTE'er er mulige i 1 forespørgsel. Her er et eksempel:
-- Hent den nuværende og tidligere sats for medarbejder 16USE AdventureWorksGO;WITH LatestEmployeePayAS( SELECT TOP 1 eph.BusinessEntityID ,eph.RateChangeDate ,eph.Rate FROM HumanResources.EmployeePayHistory ephERephOR 1.BusinessEntityID eph. .RateChangedate Desc), ForrigePloyeEepay AS (Vælg top 1 Eph.BusinessEntityID, Eph.Ratechangedate, Eph.Rate fra HumanResources.Employeepayhistory Eph Inner Deltag for seneste arbejdspladser Lep på Eph.BusinessEntityID =lep.BusinessEntityId, hvor Eph.BusinessentityId =16 og Eph.RatechangedateIDE lep.RateChangeDate BESTIL AF eph.RateChangeDate DESC)SELECT a.BusinessEntityID,a.Rate,a.RateChangeDate,b.Rate AS PreviousRateFROM LatestEmployeePay aINNER JOIN PreviousEmployeePay bON a.Encode-ID =Butsiness bON a.Encode.
Ovenstående kode bruger 2 CTE'er i én forespørgsel, nemlig LatestEmployeePay og PreviousEmployeePay .
Se en CTE flere gange
Der er mere til det forrige eksempel. Bemærk også, at du INNER kan JOIN den første CTE til den anden CTE. Endelig kan den ydre forespørgsel slutte sig til begge de 2 CTE'er. LatestEmployeePay er blevet henvist til to gange.
Videre argumenter til en SQL CTE
Argumenter, såsom variabler, kan sendes langs en CTE:
DECLARE @SalesPersonID INT =275;WITH Sales_CTEAS ( SELECT SalesPersonID, COUNT(*) AS NumberOfOrders FROM Sales.SalesOrderHeader WHERE SalesPersonID =@SalesPersonID GROUP BY SalesPersonID ) SELECT SalesPersonID ) SELECT SalesPersonFROM SalesPersonFROM SalesPersonFROM SalesPersonID>
Ovenstående kode starter med at deklarere og indstille en variabel @SalesPersonID . Værdien sendes derefter til CTE for at filtrere resultatet.
Brug i en CURSOR
En SQL-markør kan bruge en SELECT-sætning og gå gennem resultaterne. Der kan også bruges en SQL CTE med det:
DECLARE @SalesPersonID INTDECLARE @NumberofOrders INTDECLARE sales_cursor CURSOR FOR WITH Sales_CTE (SalesPersonID, NumberOfOrders) AS ( SELECT SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader GROUP WHERE ISBY SalesPersonid SalesPersonID, SalesPersonID SELECT. Salg_CTE; OPEN sales_cursorFETCH NEXT FROM sales_cursor INTO @SalesPersonID, @NumberofOrdersWHILE @@FETCH_STATUS =0 BEGIN PRINT 'SalesPersonID:' + CAST(@SalesPersonID AS VARCHAR) UDSKRIV '# af ordrer:' + CASTofOrdersROMFORDERS(@AXTCHNE) salg @SalesPersonID, @NumberofOrdersENDCLOSE sales_cursorDEALLOCATE sales_cursor;
Brug en midlertidig tabel i en rekursiv CTE
Rekursiv CTE bruger et ankerelement og et rekursivt element inden for CTE-definitionen. Det hjælper med at få hierarkier i en tabel. SQL CTE kan også bruge en midlertidig tabel til dette formål. Se et eksempel nedenfor:
-- Opret en besætningstabel. OPRET TABEL #EnterpriseDSeniorOfficers ( CrewID SMALLINT NOT NULL, FirstName NVARCHAR(30) NOT NULL, Efternavn NVARCHAR(40) NOT NULL, CrewRank NVARCHAR(50) NOT NULL, HigherRankID INT NULL, KEY PKLUSTERASCre) (KEY PKLUSTERASCre) -- Udfyld tabellen med værdier. INDSÆT I #EnterpriseDSeniorOfficers VALUES (1, N'Jean-Luc', N'Picard', N'Captain',NULL) ,(2, N'William', N'Riker', N'First Officer',1) , (3, N'Data', N'', N'Second Officer',1) ,(4, N'Worf', N'', N'Chief of Security',1) ,(5, N'Deanna' , N'Troi', N'Ship Counselor',1) ,(6, N'Beveryly', N'Crusher', N'Chief Medical Officer',1) ,(7, N'Geordi', N'LaForge' , N'Chief Engineer',1); MED DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS (SELECT HigherRankID, CrewID, CrewRank, 0 som CrewLevel FRA #EnterpriseDSeniorOfficers WHERE HigherRankID IS NULL UNION ALLE VÆLG e.HigherRankID,wRank EnterpriseDSeniorOfficers AS e INNER JOIN DirectReports AS d PÅ e.HigherRankID =d.CrewID ) VÆLG HigherRankID, CrewID, Titel, CrewLevel FRA DirectReports OPTION (MAXRECURSION 2)ORDER BY HigherRankID; SLIP TABEL #EnterpriseDSeniorOfficers
Karl forklarede ved at dissekere denne CTE. Sådan går det.
Ankermedlemmet er den første SELECT-sætning med nul (0) besætningsniveau:
VÆLG HigherRankID, CrewID, CrewRank, 0 som CrewLevel FRA #EnterpriseDSeniorOfficers WHERE HigherRankID IS NULL
Dette ankermedlem får hierarkiets rodknude. WHERE-sætningen specificerer, at rodniveauet (HigherRankID IS NULL ).
Det rekursive medlem, som vil få de underordnede noder, uddrages nedenfor:
VÆLG e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1 FRA #EnterpriseDSeniorOfficers AS e INNER JOIN DirectReports AS d PÅ e.HigherRankID =d.CrewID
Der er også en MULIGHED (MAXRECURSION 2) brugt i den ydre forespørgsel. Rekursive CTE'er kan blive problematiske, når en uendelig løkke resulterer fra den rekursive forespørgsel. MAXRECURSION 2 undgår dette rod – det begrænser loopet til kun 2 rekursioner.
Dette slutter Karls liste over, hvad der vil virke. Det er dog ikke alt, hvad vi tænker på, der virker. Det næste afsnit vil diskutere Karls resultater om disse.
Hvad virker ikke i SQL CTE?
Her har vi en liste over ting, der vil generere en fejl, når du bruger SQL CTE.
Intet semikolon foran SQL CTE
Hvis der er en erklæring før CTE, skal den erklæring afsluttes med et semikolon. WITH-sætningen kan fungere til andre formål, f.eks. i et tabeltip, så semikolonet vil fjerne tvetydighed. Udsagnet nedenfor vil forårsage en fejl:
DECLARE @SalesPersonID INTSET @SalesPersonID =275WITH Sales_CTEAS ( SELECT SalesPersonID, COUNT(*) AS NumberOfOrders FROM Sales.SalesOrderHeader WHERE SalesPersonID =@SalesPersonID GROUP BY SalesPerson ID, SalesPerson SELECT, SalesPerson
Førstegangsbrugere, der plejede ikke at afslutte udsagn med semikolon, støder på denne fejl:
Unavngivne kolonner
"Glemt at sætte et kolonnealias? Så står du til en anden fejl." Karl sagde dette i sit papir og gav også en prøvekode, som jeg deler nedenfor:
DECLARE @SalesPersonID INTSET @SalesPersonID =275;WITH Sales_CTEAS ( SELECT SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader WHERE SalesPersonID =@SalesPersonID GROUP BY SalesPersonIDSælgerPersonID ) SalesPersonF SELECT.
Tag derefter et kig på fejlmeddelelsen:
Dublerede kolonnenavne
En anden fejl relateret til #2 ovenfor bruger det samme kolonnenavn i CTE. Du kan slippe afsted med det i en normal SELECT-sætning, men ikke med en CTE. Karl havde et andet eksempel:
MED Sales_CTEAS (VÆLG SalesPersonID AS col1, COUNT(*) AS col1 FRA Sales.SalesOrderHeader GROUP BY SalesPersonID ) SELECT *FROM Sales_CTE
ORDER BY Klausul Uden TOP eller OFFSET-FETCH
Standard SQL tillader ikke ORDER BY i tabeludtryk, når vi bruger det til at sortere resultatsættene. Men hvis TOP eller OFFSET-FETCH bruges, bliver ORDER BY en filtreringshjælp.
Her er Karls eksempel ved at bruge en BESTIL AF:
WITH LatestEmployeePayAS( SELECT eph.BusinessEntityID ,eph.RateChangeDate ,eph.Rate ,eph.PayFrequency FROM HumanResources.EmployeePayHistory eph WHERE eph.BusinessEntityDERID=1.BusinessEntityRDE SELECTY DATEADD(d,365,RateChangeDate),(Rate * 0,1) + Rate,PayFrequency,GETDATE()FROM LatestEmployeePay
Bemærk, at det er det samme eksempel, som vi havde tidligere, men denne gang er TOP ikke angivet. Tjek fejlen:
Antallet af kolonner er ikke det samme som kolonnelistens definition
Karl brugte det samme rekursive CTE-eksempel, men han tog en kolonne ud i ankerelementet:
WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS ( SELECT HigherRankID, CrewID FROM #EnterpriseDSeniorOfficers WHERE HigherRankID IS NULL UNION ALLE VÆLG e.HigherRankID, e.CrewID, #e.CrewID, +e. EnterpriseDSeniorOfficers AS e INNER JOIN DirectReports AS d PÅ e.HigherRankID =d.CrewID ) VÆLG HigherRankID, CrewID, Title, CrewLevel FRA DirectReports BESTIL EFTER HigherRankID;
Ovenstående kode bruger en liste med tre kolonner med en ekstern form, men ankermedlemmet havde kun 2 kolonner. Det er ikke tilladt. At lave en fejl som denne vil udløse en fejl:
Andre ting, der ikke er tilladt i en CTE
Bortset fra listen ovenfor, er her et par flere af Karls resultater, der udløser fejl, hvis du bruger det ved en fejl i en SQL CTE.
- Brug af SELECT INTO, OPTION-klausul med forespørgselstip og brug FOR Browse.
- Forskellige data og typer i ankermedlemskolonnerne sammenlignet med de rekursive medlemskolonner.
- At have følgende nøgleord i et rekursivt medlem af en rekursiv CTE:
- TOP
- YDRE JOIN (Men INNER JOIN er tilladt)
- GRUPPER EFTER OG HAR
- Underforespørgsler
- VÆLG DISTINCT
- Brug af skalær aggregering.
- Brug af underforespørgsler i et rekursivt medlem.
- Har indlejret SQL CTE'er.
SQL CTE vs. midlertidige tabeller vs. underforespørgsler
Til tider kan du omskrive en SQL CTE ved hjælp af en underforespørgsel. Til tider kan du også nedbryde en SQL CTE ved hjælp af midlertidige tabeller af ydeevnemæssige årsager. Som enhver anden forespørgsel skal du tjekke den faktiske udførelsesplan og STATISTICS IO for at vide, hvilken mulighed du skal vælge. SQL CTE kan være venlig for øjnene, men hvis du rammer en ydeevnevæg, så brug en anden mulighed . Ingen mulighed er hurtigere end den anden.
Lad os undersøge tre forespørgsler fra Karls artikler, der giver de samme resultater. Den ene bruger en SQL CTE, den anden bruger en underforespørgsel, og den tredje bruger en midlertidig tabel. For nemheds skyld brugte Karl et lille resultatsæt.
Koden og resultatsættet
Det startede med at bruge flere SQL CTE'er.
WITH LatestEmployeePayAS( SELECT TOP 1 eph.BusinessEntityID ,eph.RateChangeDate ,eph.Rate FROM HumanResources.EmployeePayHistory eph WHERE eph.BusinessEntityID =16 ORDER DEVELGT.Ph.Ph.Ph.VÆLG.Ph.Ph.Pr. BusinessEntityID ,eph.RateChangeDate ,eph.Rate FROM HumanResources.EmployeePayHistory eph INNER JOIN LatestEmployeePay lep ON eph.BusinessEntityID =lep.BusinessEntityID HVOR eph.BusinessEntityID HVOR eph.BusinessEntityChangeDato.Vælg.DatoeChange.DatoeChange.Dato.Vælg BusinessEntityID,a.Rate,a.RateChangeDate,b.Rate AS PreviousRateFROM LatestEmployeePay aINNER JOIN PreviousEmployeePay b PÅ a.BusinessEntityID =b.BusinessEntityID
Den næste er en underforespørgsel. Som du bemærker, ser CTE'en modulopbygget og læsbar ud, men underforespørgslen nedenfor er kortere:
SELECT TOP 1 eph.BusinessEntityID,eph.Rate,eph.RateChangeDate,(SELECT TOP 1 eph1.Rate FROM HumanResources.EmployeePayHistory eph1 WHERE eph1.BusinessEntityID=16 AND hangeChang.DateRephDERChang.Eph1DatoR .RateChangeDate DESC) AS PreviousRateFROM HumanResources.EmployeePayHistory ephWHERE eph.BusinessEntityID =16ORDER BY eph.RateChangeDate DESC;
Karl forsøgte også at opdele det i små bidder af kode og derefter kombinere resultaterne ved hjælp af midlertidige tabeller.
VÆLG TOP 1 eph.BusinessEntityID,eph.RateChangeDate,eph.RateINTO #LatestPayFROM HumanResources.EmployeePayHistory eph WHERE eph.BusinessEntityID =16ORDER BY eph.RateChangeph.DateChangeTIN #PreviousPayFrom HumanResources.Employeepayhistory Ephinner Deltag i #LatestPay LEP på Eph.BusinessEntityID =lep.BusinessEntityIdwhere Eph.BusinessEntityID =16 og Eph.Ratechangedate
Tag et kig på disse tre måder, hvorpå du kan få den nuværende og tidligere løn for medarbejder 16. De er de samme:
De logiske læsninger
Hvad bruger flest SQL Server-ressourcer? Lad os se på STATISTIK IO. Karl brugte statisticsparser.com til at formatere resultaterne pænt – godt for os.
Figur 7 viser logiske læsninger af brug af en CTE vs. brug af en underforespørgsel:
Se derefter de samlede logiske læsninger, når koden opdeles i små bidder ved hjælp af midlertidige tabeller:
Så hvad bruger flere ressourcer? Karl rangerede dem for klarhedens skyld.
- Underforespørgsel – 4 logiske læsninger (VINDER!).
- SQL CTE – 6 logiske læsninger.
- Temperaturtabeller – 8 logiske læsninger.
I dette eksempel vil den hurtigste være underforespørgslen.
Den faktiske udførelsesplan
For at give mening med de logiske læsninger, vi fik fra STATISTICS IO, tjekkede Karl også den faktiske udførelsesplan. Han startede fra CTE:
Vi kan observere et par ting fra denne plan:
- Seneste EmployeePay CTE blev evalueret to gange, når det blev brugt i den ydre forespørgsel, og når det er knyttet til PreviousEmployeePay . Så vi ser 2 TOP noder for dette.
- Vi ser PreviousEmployeePay evalueret én gang.
Se derefter den faktiske udførelsesplan for forespørgslen med en underforespørgsel:
Der er et par indlysende ting her:
- Planen er enklere.
- Det er enklere, fordi underforespørgslen for at få den seneste løn kun evalueres én gang.
- Ikke underligt, at de logiske læsninger er mindre sammenlignet med de logiske læsninger af forespørgslen med en CTE.
Endelig, her er den faktiske udførelsesplan, da Karl brugte midlertidige tabeller:
Da det er en batch af tre udsagn, ser vi også tre diagrammer i planen. Alle tre er enkle, men den kollektive plan er ikke så enkel som planen for forespørgslen med underforespørgslen.
Tidsstatistikken
Ved at bruge dbForge Studio til SQL Server-løsningen kan du sammenligne tidsstatistikken i Query Profiler. Til det skal du holde CTRL-tasten nede og klikke på resultatnavnene for hver forespørgsel i eksekveringshistorikken:
Tidsstatistikken stemmer overens med de logiske aflæsninger og den faktiske udførelsesplan. Underforespørgslen kørte hurtigst (88ms). Det efterfølges af CTE (199ms). Den sidste er brugen af midlertidige tabeller (536ms).
Så hvad har vi lært af Karl?
I dette særlige eksempel så vi, at det er meget bedre at bruge en underforespørgsel, når vi vil have den hurtigste mulighed. Det kan dog være en anden historie, hvis kravsættet ikke er sådan.
Tjek altid STATISTICS IO og de faktiske udførelsesplaner for at vide, hvilken teknik du skal bruge.
Takeaways
Jeg håber ikke, du stødte hovedet mod væggen for at forstå, hvad en CTE (Common Table Expressions) er. Ellers kan du få en CTE (kronisk traumatisk encefalopati). Spøg til side, hvad afslørede vi?
- Almindelige tabeludtryk er midlertidigt navngivne resultatsæt. Det er tættere på en underforespørgsel i adfærd og omfang, men klarere og mere modulært. Den har også et navn.
- SQL CTE er for at forenkle kode, ikke for at gøre din forespørgsel hurtigere.
- Syv ting, vi har lært, vil fungere på SQL CTE.
- Fem ting vil udløse en fejl.
- Vi har også bekræftet endnu en gang, at STATISTICS IO og Faktisk udførelsesplan altid vil give dig en bedre dømmekraft. Det er sandt, hvis du sammenligner en CTE med en underforespørgsel og en batch ved hjælp af midlertidige tabeller.
Nød det? Så venter knapperne på de sociale medier på at blive trykket. Vælg din favorit og del kærligheden!
Læs også
Hvordan CTE kan hjælpe med at skrive komplekse, kraftfulde forespørgsler:et præstationsperspektiv