sql >> Databasteknik >  >> RDS >> Database

SQL CASE:Känn till och undvik 3 mindre kända problem

SQL CASE? En tårta!

Verkligen?

Inte förrän du stöter på tre besvärliga problem som kan orsaka körtidsfel och långsam prestanda.

Om du försöker skanna underrubrikerna för att se vad problemen är, kan jag inte klandra dig. Läsare, inklusive jag, är otåliga.

Jag litar på att du redan kan grunderna i SQL CASE, så jag kommer inte att tråka ut dig med långa introduktioner. Låt oss gräva i en djupare förståelse för vad som händer under huven.

1. SQL CASE utvärderas inte alltid sekventiellt

Uttryck i Microsoft SQL CASE-satsen utvärderas oftast sekventiellt eller från vänster till höger. Det är dock en annan historia när du använder den med aggregerade funktioner. Låt oss ta ett exempel:

-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;

Ovanstående kod ser normal ut. Om jag frågar dig vad som är resultatet av dessa påståenden kommer du förmodligen att säga 1. Visuell inspektion säger oss att eftersom @value är satt till 0. När @value är 0 är resultatet 1.

Men så är inte fallet här. Ta en titt på det verkliga resultatet från SQL Server Management Studio:

Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.

Men varför?

När villkorliga uttryck använder aggregerade funktioner som MAX() i SQL CASE, utvärderas det först. Således kommer MAX(1/@värde) att orsaka divisionen med noll-fel eftersom @värde är noll.

Denna situation är mer besvärlig när den är dold. Jag ska förklara det senare.

2. Enkelt SQL CASE-uttryck utvärderar flera gånger

Så vad?

Bra fråga. Sanningen är att det inte finns några problem alls om du använder bokstavliga eller enkla uttryck. Men om du använder underfrågor som ett villkorligt uttryck får du en stor överraskning.

Innan du provar exemplet nedan kanske du vill återställa en kopia av databasen härifrån. Vi kommer att använda det för resten av exemplen.

Tänk nu på denna mycket enkla fråga:


SELECT TOP 1 manufacturerID FROM SportsCars

Det är väldigt enkelt, eller hur? Den returnerar 1 rad med 1 kolumn med data. STATISTICS IO avslöjar minimala logiska läsningar.

Snabb anteckning :För de oinitierade, med högre logiska läsningar gör en fråga långsam. Läs detta för mer information.

Execution Plan avslöjar också en enkel process:

Låt oss nu lägga in den frågan i ett CASE-uttryck som en underfråga:

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

Analys

Håll tummarna för det här kommer att blåsa bort logiska läsningar 4 gånger om.

Överraskning! Jämfört med figur 1 med bara 2 logiska avläsningar är detta 4 gånger högre. Därför är frågan 4 gånger långsammare. Hur kunde det hända? Vi såg bara underfrågan en gång.

Men det är inte slutet på historien. Kolla in exekveringsplanen:

Vi ser 4 instanser av top- och indexskanningsoperatorerna i figur 4. Om varje topp- och indexskanning förbrukar 2 logiska läsningar, förklarar det varför de logiska läsningarna blev 8 i figur 3. Och eftersom varje topp- och indexskanning har en kostnad på 25 % , det säger oss också att de är likadana.

Men det slutar inte där. Egenskaperna för Compute Scalar-operatorn avslöjar hur hela påståendet behandlas.

Vi ser 4 CASE WHEN-uttryck som kommer från Compute Scalar-operatorn Defined Values. Det ser ut som om vårt enkla CASE-uttryck blev ett sökt CASE-uttryck så här:

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;

Låt oss sammanfatta. Det fanns 2 logiska avläsningar för varje Top- och Index Scan-operatör. Detta multiplicerat med 4 ger 8 logiska läsningar. Vi såg också 4 CASE WHEN-uttryck i Compute Scalar-operatorn.

Till slut utvärderades underfrågan i det enkla CASE-uttrycket fyra gånger. Detta fördröjer din fråga.

Hur man undviker flera utvärderingar av en delfråga i ett enkelt CASE-uttryck

För att undvika sådana prestandaproblem som flera CASE-satser i SQL, måste vi skriva om frågan.

Lägg först resultatet av underfrågan i en variabel. Använd sedan den variabeln i tillståndet för det enkla SQL Server CASE-uttrycket, så här:

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;

Är detta en bra fix? Låt oss se de logiska läsningarna i STATISTIK IO:

Vi ser lägre logiska läsningar från den modifierade frågan. Att ta bort underfrågan och tilldela resultatet till en variabel är mycket bättre. Hur är det med genomförandeplanen? Se det nedan.

Operatören Top och Index Scan visades bara en gång, inte fyra gånger. Underbart!

Takeaway :Använd inte en underfråga som villkor i CASE-uttrycket. Om du behöver hämta ett värde, lägg resultatet av underfrågan i en variabel först. Använd sedan den variabeln i CASE-uttrycket.

3. Dessa tre inbyggda funktioner förvandlas i hemlighet till SQL CASE

Det finns en hemlighet, och SQL Server CASE-satsen har något att göra med det. Om du inte vet hur dessa 3 funktioner beter sig kommer du inte att veta att du begår ett misstag som vi försökte undvika i punkterna #1 och #2 tidigare. Här är de:

  • IIF
  • COALESCE
  • VÄLJ

Låt oss undersöka dem en efter en.

IIF

Jag använde Immediate IF, eller IIF, i Visual Basic och Visual Basic for Applications. Detta motsvarar också C#s ternära operator: ? :.

Denna funktion givet ett villkor returnerar 1 av de 2 argumenten baserat på villkorsresultatet. Och den här funktionen finns även i T-SQL. CASE-satsen i WHERE-satsen kan användas i SELECT-satsen

Men det är bara en sugarcoat med ett längre CASE-uttryck. Hur vet vi? Låt oss undersöka ett exempel.

SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');

Resultatet av denna fråga är "Nej". Kolla dock in exekveringsplanen tillsammans med egenskaperna för Compute Scalar.

Eftersom IIF är CASE NÄR vad tror du kommer att hända om du kör något sånt här?

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

Detta kommer att resultera i ett Divide by Noll-fel om @noOfPayments är 0. Detsamma hände vid punkt #1 tidigare.

Du kanske undrar vad som orsakar detta fel eftersom ovanstående fråga kommer att resultera i TRUE och bör returnera 83333.33. Kontrollera punkt #1 igen.

Således, om du har fastnat med ett fel som detta när du använder IIF, är SQL CASE boven.

COALESCE

COALESCE är också en genväg till ett SQL CASE-uttryck. Den utvärderar värdelistan och returnerar det första icke-nullvärdet. I den tidigare artikeln om COALESCE presenterade jag ett exempel som utvärderar en underfråga två gånger. Men jag använde en annan metod för att avslöja SQL CASE i exekveringsplanen. Här är ett annat exempel som kommer att använda samma teknik.

SELECT 
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car 
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID

Låt oss se exekveringsplanen och de beräknade skalärdefinierade värdena.

SQL CASE är okej. Nyckelordet COALESCE finns ingenstans i fönstret Definierade värden. Detta bevisar hemligheten bakom denna funktion.

Men det är inte allt. Hur många gånger såg du [Fordon].[dbo].[Styles].[Style] i fönstret Definierade värden? DUBBELT! Detta överensstämmer med den officiella Microsoft-dokumentationen. Föreställ dig om ett av argumenten i COALESCE är en underfråga. Fördubbla sedan de logiska läsningarna och få den långsammare exekveringen också.

VÄLJ

Till sist, VÄLJ. Detta liknar MS Access CHOOSE-funktionen. Den returnerar 1 värde från en lista med värden baserat på en indexposition. Den fungerar också som ett index i en array.

Låt oss se om vi kan gräva omvandlingen till ett SQL CASE med ett exempel. Kolla in koden nedan:

;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

Där är vårt VÄLJ-exempel. Låt oss nu kontrollera exekveringsplanen och de beräknade skalärdefinierade värdena:

Ser du nyckelordet VÄLJ i fönstret Definierade värden i figur 10? Vad sägs om CASE NÄR?

Liksom de tidigare exemplen är denna VÄLJ-funktion bara en sugarcoat till ett längre CASE-uttryck. Och eftersom frågan har 2 objekt för VÄLJ, dök CASE NÄR nyckelorden upp två gånger. Se fönstret Definierade värden i en röd ruta.

Däremot har vi flera CASE WHEN i SQL här. Det beror på CASE-uttrycket i den inre frågan i CTE. Om du tittar noga visas den delen av den inre frågan två gånger också.

Hämtmat

Nu när hemligheterna är ute, vad har vi lärt oss?

  1. SQL CASE beter sig annorlunda när aggregerade funktioner används. Var försiktig när du skickar argument till aggregerade funktioner som MIN, MAX eller COUNT.
  2. Ett enkelt CASE-uttryck kommer att utvärderas flera gånger. Lägg märke till det och undvik att skicka en underfråga. Även om det är syntaktisk korrekt, kommer det att fungera dåligt.
  3. IIF, CHOOSE och COALESCE har smutsiga hemligheter. Ha det i åtanke innan du skickar värden till dessa funktioner. Det kommer att förvandlas till ett SQL CASE. Beroende på värdena orsakar du antingen ett fel eller en prestationsstraff.

Jag hoppas att denna annorlunda syn på SQL CASE har varit användbar för dig. Om så är fallet kanske dina utvecklarvänner också gillar det. Vänligen dela det på dina favoritplattformar för sociala medier. Och låt oss veta vad du tycker om det i kommentarsektionen.


  1. Node.js MSSQL tedius Anslutningsfel:Det gick inte att ansluta till localhost:1433 - anslut ECONNREFUSED

  2. Var är mina patchar?

  3. Distribuera Django + Python 3 + PostgreSQL till AWS Elastic Beanstalk

  4. Extrahera året från ett datum i PostgreSQL