SQL CASE? Et stykke kage!
Virkelig?
Ikke før du støder ind i 3 besværlige problemer, der kan forårsage runtime fejl og langsom ydeevne.
Hvis du prøver at scanne underoverskrifterne for at se, hvad problemerne er, kan jeg ikke bebrejde dig. Læsere, inklusive mig, er utålmodige.
Jeg stoler på, at du allerede kender det grundlæggende i SQL CASE, så jeg vil ikke kede dig med lange introduktioner. Lad os grave ind i en dybere forståelse af, hvad der sker under motorhjelmen.
1. SQL CASE evaluerer ikke altid sekventielt
Udtryk i Microsoft SQL CASE-sætningen evalueres for det meste sekventielt eller fra venstre mod højre. Det er dog en anden historie, når du bruger det med samlede funktioner. Lad os tage et eksempel:
-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;
Ovenstående kode ser normal ud. Hvis jeg spørger dig, hvad resultatet af disse udsagn er, vil du sandsynligvis sige 1. Visuel inspektion fortæller os, at fordi @værdi er sat til 0. Når @værdien er 0, er resultatet 1.
Men det er ikke tilfældet her. Tag et kig på det rigtige resultat fra SQL Server Management Studio:
Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.
Men hvorfor?
Når betingede udtryk bruger aggregerede funktioner som MAX() i SQL CASE, evalueres det først. Således vil MAX(1/@værdi) forårsage division med nul fejl, fordi @værdi er nul.
Denne situation er mere besværlig, når den er skjult. Jeg forklarer det senere.
2. Simpelt SQL CASE-udtryk evaluerer flere gange
Hvad så?
Godt spørgsmål. Sandheden er, at der overhovedet ikke er nogen problemer, hvis du bruger bogstavelige eller simple udtryk. Men hvis du bruger underforespørgsler som et betinget udtryk, får du en stor overraskelse.
Før du prøver eksemplet nedenfor, vil du måske gendanne en kopi af databasen herfra. Vi vil bruge det til resten af eksemplerne.
Overvej nu denne meget enkle forespørgsel:
SELECT TOP 1 manufacturerID FROM SportsCars
Det er meget enkelt, ikke? Det returnerer 1 række med 1 kolonne med data. STATISTICS IO afslører minimale logiske læsninger.
Hurtig bemærkning :For de uindviede gør det at have højere logiske læsninger en forespørgsel langsom. Læs dette for flere detaljer.
Udførelsesplanen afslører også en simpel proces:
Lad os nu sætte den forespørgsel ind i et CASE-udtryk som en underforespørgsel:
-- Using a subquery in a SQL CASE
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE (SELECT TOP 1 manufacturerID FROM SportsCars)
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Analyse
Kryds fingre, for dette vil blæse logiske læsninger væk 4 gange.
Overraskelse! Sammenlignet med figur 1 med kun 2 logiske aflæsninger, er dette 4 gange højere. Således er forespørgslen 4 gange langsommere. Hvordan kunne det ske? Vi så kun underforespørgslen én gang.
Men det er ikke slutningen på historien. Tjek udførelsesplanen:
Vi ser 4 forekomster af top- og indeksscanningsoperatorerne i figur 4. Hvis hver top- og indeksscanning bruger 2 logiske læsninger, forklarer det, hvorfor de logiske læsninger blev 8 i figur 3. Og da hver top- og indeksscanning har en omkostning på 25 % , det fortæller os også, at de er de samme.
Men det slutter ikke der. Egenskaberne for Compute Scalar-operatoren afslører, hvordan hele sætningen behandles.
Vi ser 4 CASE WHEN-udtryk, der kommer fra Compute Scalar-operatoren Defined Values. Det ser ud til, at vores simple CASE-udtryk blev et søgt CASE-udtryk som dette:
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 6 THEN 'Alfa Romeo'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 21 THEN 'Aston Martin'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 64 THEN 'Ferrari'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Lad os opsummere. Der var 2 logiske aflæsninger for hver top- og indeksscanningsoperatør. Dette ganget med 4 giver 8 logiske aflæsninger. Vi så også 4 CASE WHEN-udtryk i Compute Scalar-operatoren.
Til sidst blev underforespørgslen i det simple CASE-udtryk evalueret 4 gange. Dette vil forsinke din forespørgsel.
Sådan undgår du flere evalueringer af en underforespørgsel i et simpelt CASE-udtryk
For at undgå sådanne problemer med ydeevne som flere CASE-sætninger i SQL, er vi nødt til at omskrive forespørgslen.
Indsæt først resultatet af underforespørgslen i en variabel. Brug derefter denne variabel i tilstanden af det simple SQL Server CASE-udtryk, som dette:
DECLARE @manufacturer NVARCHAR(50)
DECLARE @ManufacturerID INT -- create a new variable
-- store the result of the subquery in a variable
SET @ManufacturerID = (SELECT TOP 1 manufacturerID FROM SportsCars)
-- use the new variable in the simple CASE expression
SET @manufacturer = (CASE @ManufacturerID
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Er dette en god løsning? Lad os se de logiske læsninger i STATISTICS IO:
Vi ser lavere logiske læsninger fra den ændrede forespørgsel. At tage underforespørgslen ud og tildele resultatet til en variabel er meget bedre. Hvad med udførelsesplanen? Se det nedenfor.
Top- og indeksscanningsoperatøren dukkede kun op én gang, ikke 4 gange. Vidunderligt!
Takeaway :Brug ikke en underforespørgsel som en betingelse i CASE-udtrykket. Hvis du har brug for at hente en værdi, skal du sætte resultatet af underforespørgslen i en variabel først. Brug derefter denne variabel i CASE-udtrykket.
3. Disse 3 indbyggede funktioner transformeres i hemmelighed til SQL CASE
Der er en hemmelighed, og SQL Server CASE-sætningen har noget at gøre med det. Hvis du ikke ved, hvordan disse 3 funktioner opfører sig, vil du ikke vide, at du begår en fejl, som vi forsøgte at undgå i punkt #1 og #2 tidligere. Her er de:
- IIF
- COALESCE
- VÆLG
Lad os undersøge dem én efter én.
IIF
Jeg brugte Immediate IF eller IIF i Visual Basic og Visual Basic for Applications. Dette svarer også til C#s ternære operator:
Denne funktion givet en betingelse vil returnere 1 af de 2 argumenter baseret på betingelsesresultatet. Og denne funktion er også tilgængelig i T-SQL. CASE-sætning i WHERE-sætningen kan bruges i SELECT-sætningen
Men det er bare en sugarcoat af et længere CASE-udtryk. Hvordan ved vi det? Lad os se på et eksempel.
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');
Resultatet af denne forespørgsel er 'Nej'. Tjek dog udførelsesplanen sammen med egenskaberne for Compute Scalar.
Da IIF er CASE HVORNÅR, hvad tror du, der vil ske, hvis du udfører noget som dette?
DECLARE @averageCost MONEY = 1000000.00;
DECLARE @noOfPayments TINYINT = 0; -- intentional to force the error
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'SF90 Spider', 83333.33,MIN(@averageCost / @noOfPayments));
Dette vil resultere i en Divide by Zero-fejl, hvis @noOfPayments er 0. Det samme skete ved punkt #1 tidligere.
Du undrer dig måske over, hvad der forårsager denne fejl, fordi ovenstående forespørgsel vil resultere i TRUE og burde returnere 83333.33. Tjek punkt #1 igen.
Så hvis du sidder fast med en fejl som denne, når du bruger IIF, er SQL CASE synderen.
COALESCE
COALESCE er også en genvej til et SQL CASE-udtryk. Den evaluerer listen over værdier og returnerer den første ikke-nul værdi. I den forrige artikel om COALESCE præsenterede jeg et eksempel, der evaluerer en underforespørgsel to gange. Men jeg brugte en anden metode til at afsløre SQL CASE i udførelsesplanen. Her er endnu et eksempel, der vil bruge de samme teknikker.
SELECT
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID
Lad os se eksekveringsplanen og de beregnede skalardefinerede værdier.
SQL CASE er i orden. Nøgleordet COALESCE er ingen steder i vinduet Definerede værdier. Dette beviser hemmeligheden bag denne funktion.
Men det er ikke alt. Hvor mange gange så du [Køretøjer].[dbo].[Styles].[Style] i vinduet Definerede værdier? TO GANGE! Dette er i overensstemmelse med den officielle Microsoft-dokumentation. Forestil dig, hvis et af argumenterne i COALESCE er en underforespørgsel. Fordoble derefter de logiske læsninger og få også den langsommere udførelse.
VÆLG
VÆLG endelig. Dette svarer til MS Access CHOOSE-funktionen. Det returnerer 1 værdi fra en liste over værdier baseret på en indeksposition. Det fungerer også som et indeks i et array.
Lad os se, om vi kan grave transformationen til en SQL CASE med et eksempel. Tjek koden nedenfor:
;WITH McLarenCars AS
(
SELECT
CASE
WHEN sc.Model IN ('Artura','Speedtail','P1/ P1 GTR','P1 LM') THEN '1'
ELSE '2'
END AS [type]
,sc.Model
,s.Style
FROM SportsCars sc
INNER JOIN Styles s ON sc.StyleID = s.StyleID
WHERE sc.ManufacturerID = 108
)
SELECT
Model
,Style
,CHOOSE([Type],'Hybrid','Gasoline') AS [type]
FROM McLarenCars
Der er vores CHOOSE eksempel. Lad os nu tjekke eksekveringsplanen og de beregnede skalardefinerede værdier:
Kan du se nøgleordet CHOOSE i vinduet Definerede værdier i figur 10? Hvad med CASE NÅR?
Ligesom de foregående eksempler er denne VÆLG-funktion blot en sukkercoat til et længere CASE-udtryk. Og da forespørgslen har 2 emner til CHOOSE, dukkede CASE WHEN søgeordene op to gange. Se vinduet Definerede værdier omgivet af en rød boks.
Vi har dog flere CASE WHEN i SQL her. Det er på grund af CASE-udtrykket i den indre forespørgsel i CTE. Hvis du ser godt efter, vises den del af den indre forespørgsel også to gange.
Takeaways
Nu hvor hemmelighederne er ude, hvad har vi så lært?
- SQL CASE opfører sig anderledes, når der bruges aggregerede funktioner. Vær forsigtig, når du sender argumenter til aggregerede funktioner som MIN, MAX eller COUNT.
- Et simpelt CASE-udtryk vil evalueres flere gange. Læg mærke til det, og undgå at sende en underforespørgsel. Selvom det er syntaktisk korrekt, vil det fungere dårligt.
- IIF, CHOOSE og COALESCE har beskidte hemmeligheder. Husk det, før du overfører værdier til disse funktioner. Det vil forvandles til en SQL CASE. Afhængigt af værdierne forårsager du enten en fejl eller en præstationsstraf.
Jeg håber, at denne anderledes opfattelse af SQL CASE har været nyttig for dig. Hvis ja, kan dine udviklervenner også lide det. Del det venligst på dine foretrukne sociale medieplatforme. Og lad os vide, hvad du synes om det i kommentarsektionen.