Database
 sql >> Database >  >> RDS >> Database

SQL CASE:conosci ed evita 3 problemi meno noti

CASO SQL? Pezzo di torta!

Davvero?

Non finché non ti imbatti in 3 fastidiosi problemi che possono causare errori di runtime e prestazioni lente.

Se stai cercando di scansionare i sottotitoli per vedere quali sono i problemi, non posso biasimarti. I lettori, me compreso, sono impazienti.

Confido che tu conosca già le basi di SQL CASE, quindi non ti annoierò con lunghe presentazioni. Analizziamo più a fondo ciò che sta accadendo sotto il cofano.

1. SQL CASE non valuta sempre in sequenza

Le espressioni nell'istruzione CASE di Microsoft SQL vengono valutate principalmente in sequenza o da sinistra a destra. È una storia diversa, tuttavia, quando lo si utilizza con funzioni aggregate. Facciamo un esempio:

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

Il codice sopra sembra normale. Se ti chiedo qual è il risultato di queste affermazioni, probabilmente dirai 1. L'ispezione visiva ci dice che poiché @value è impostato su 0. Quando @value è 0, il risultato è 1.

Ma non è questo il caso. Dai un'occhiata al risultato reale di SQL Server Management Studio:

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

Ma perché?

Quando le espressioni condizionali utilizzano funzioni aggregate come MAX() in SQL CASE, vengono valutate per prime. Pertanto, MAX(1/@valore) causerà l'errore di divisione per zero perché @valore è zero.

Questa situazione è più problematica quando è nascosta. Te lo spiego dopo.

2. La semplice espressione SQL CASE valuta più volte

E allora?

Buona domanda. La verità è che non ci sono problemi se usi letterali o espressioni semplici. Ma se usi le sottoquery come espressione condizionale, avrai una grande sorpresa.

Prima di provare l'esempio seguente, potresti voler ripristinare una copia del database da qui. Lo useremo per il resto degli esempi.

Ora, considera questa query molto semplice:


SELECT TOP 1 manufacturerID FROM SportsCars

È molto semplice, vero? Restituisce 1 riga con 1 colonna di dati. STATISTICS IO rivela letture logiche minime.

Nota rapida :Per chi non lo sapesse, avere letture logiche più elevate rende una query lenta. Leggi questo per maggiori dettagli.

Il Piano di Esecuzione rivela anche un processo semplice:

Ora, inseriamo quella query in un'espressione CASE come sottoquery:

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

Analisi

Incrocia le dita perché questo farà esplodere le letture logiche 4 volte.

Sorpresa! Rispetto alla Figura 1 con solo 2 letture logiche, questo è 4 volte superiore. Pertanto, la query è 4 volte più lenta. Come potrebbe accadere? Abbiamo visto la sottoquery solo una volta.

Ma questa non è la fine della storia. Dai un'occhiata al Piano di esecuzione:

Nella Figura 4 vediamo 4 istanze degli operatori Top e Index Scan. Se ogni Top e Index Scan consuma 2 letture logiche, questo spiega perché le letture logiche sono diventate 8 nella Figura 3. E poiché ogni Top e Index Scan hanno un costo del 25% , ci dice anche che sono la stessa cosa.

Ma non finisce qui. Le proprietà dell'operatore Calcola scalare rivelano come viene trattata l'intera istruzione.

Vediamo 4 espressioni CASE WHEN provenienti dall'operatore Compute Scalar Defined Values. Sembra che la nostra semplice espressione CASE sia diventata un'espressione CASE cercata come questa:

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;

Ricapitoliamo. C'erano 2 letture logiche per ogni operatore Top e Index Scan. Questo moltiplicato per 4 fa 8 letture logiche. Abbiamo anche visto 4 espressioni CASE WHEN nell'operatore Compute Scalar.

Alla fine, la sottoquery nell'espressione CASE semplice è stata valutata 4 volte. Questo ritarderà la tua richiesta.

Come evitare valutazioni multiple di una sottoquery in una semplice espressione CASE

Per evitare problemi di prestazioni come l'istruzione CASE multipla in SQL, è necessario riscrivere la query.

Innanzitutto, inserisci il risultato della sottoquery in una variabile. Quindi, usa quella variabile nella condizione della semplice espressione CASE di SQL Server, in questo modo:

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;

È una buona soluzione? Vediamo le letture logiche in STATISTICS IO:

Vediamo letture logiche inferiori dalla query modificata. Eliminare la sottoquery e assegnare il risultato a una variabile è molto meglio. Che ne dici del piano di esecuzione? Vedi sotto.

L'operatore Top and Index Scan è apparso solo una volta, non 4 volte. Meraviglioso!

Da asporto :non utilizzare una sottoquery come condizione nell'espressione CASE. Se è necessario recuperare un valore, inserire prima il risultato della sottoquery in una variabile. Quindi, usa quella variabile nell'espressione CASE.

3. Queste 3 funzioni integrate si trasformano segretamente in SQL CASE

C'è un segreto e l'istruzione CASE di SQL Server ha qualcosa a che fare con esso. Se non sai come si comportano queste 3 funzioni, non saprai che stai commettendo un errore che abbiamo cercato di evitare nei punti 1 e 2 in precedenza. Eccoli:

  • IIF
  • COALESCE
  • SCEGLI

Esaminiamoli uno per uno.

IIF

Ho usato Immediate IF, o IIF, in Visual Basic e Visual Basic for Applications. Questo equivale anche all'operatore ternario di C#: ? :.

Questa funzione data una condizione restituirà 1 dei 2 argomenti in base al risultato della condizione. E questa funzione è disponibile anche in T-SQL. L'istruzione CASE nella clausola WHERE può essere utilizzata all'interno dell'istruzione SELECT

Ma è solo un soprabito di un'espressione CASE più lunga. Come lo sappiamo? Esaminiamo un esempio.

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

Il risultato di questa query è "No". Tuttavia, controlla il piano di esecuzione insieme alle proprietà di Compute Scalar.

Poiché IIF è CASE WHEN, cosa pensi che accadrà se esegui qualcosa del genere?

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

Ciò comporterà un errore Dividi per zero se @noOfPayments è 0. Lo stesso è accaduto al punto 1 in precedenza.

Potresti chiederti cosa causa questo errore perché la query precedente risulterà TRUE e dovrebbe restituire 83333.33. Controlla di nuovo il punto 1.

Pertanto, se sei bloccato con un errore come questo quando usi IIF, SQL CASE è il colpevole.

COALESCE

COALESCE è anche una scorciatoia di un'espressione SQL CASE. Valuta l'elenco di valori e restituisce il primo valore non nullo. Nel precedente articolo su COALESCE, ho presentato un esempio che valuta una sottoquery due volte. Ma ho usato un altro metodo per rivelare SQL CASE nel piano di esecuzione. Ecco un altro esempio che utilizzerà le stesse tecniche.

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

Vediamo il piano di esecuzione e il calcolo dei valori scalari definiti.

SQL CASE va bene. La parola chiave COALESCE non è da nessuna parte nella finestra Valori definiti. Questo dimostra il segreto dietro questa funzione.

Ma non è tutto. Quante volte hai visto [Veicoli].[dbo].[Stili].[Stile] nella finestra Valori definiti? DUE VOLTE! Ciò è coerente con la documentazione ufficiale di Microsoft. Immagina se uno degli argomenti in COALESCE fosse una sottoquery. Quindi, raddoppia le letture logiche e ottieni anche l'esecuzione più lenta.

SCEGLI

Infine, SCEGLI. Questo è simile alla funzione SCEGLI MS Access. Restituisce 1 valore da un elenco di valori basato su una posizione di indice. Agisce anche come indice in un array.

Vediamo se riusciamo a scavare la trasformazione in un CASE SQL con un esempio. Controlla il codice qui sotto:

;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

C'è il nostro esempio CHOOSE. Ora, controlliamo il piano di esecuzione e il calcolo dei valori scalari definiti:

Vedi la parola chiave SCEGLI nella finestra Valori definiti nella Figura 10? Che ne dici di CASE QUANDO?

Come gli esempi precedenti, questa funzione CHOOSE è solo un riassunto di un'espressione CASE più lunga. E poiché la query contiene 2 elementi per SCEGLIERE, le parole chiave CASE WHEN sono apparse due volte. Vedi la finestra Valori definiti racchiusa in un riquadro rosso.

Tuttavia, qui abbiamo più CASE WHEN in SQL. Ciò è dovuto all'espressione CASE nella query interna del CTE. Se guardi attentamente, anche quella parte della query interna appare due volte.

Da asporto

Ora che i segreti sono stati svelati, cosa abbiamo imparato?

  1. SQL CASE si comporta in modo diverso quando vengono utilizzate funzioni aggregate. Fai attenzione quando passi argomenti per aggregare funzioni come MIN, MAX o COUNT.
  2. Una semplice espressione CASE valuterà più volte. Notalo ed evita di passare una sottoquery. Sebbene sia sintatticamente corretto, funzionerà male.
  3. IIF, CHOOSE e COALESCE hanno sporchi segreti. Tienilo a mente prima di passare valori a quelle funzioni. Si trasformerà in un CASE SQL. A seconda dei valori, potresti causare un errore o una penalizzazione delle prestazioni.

Spero che questa diversa interpretazione di SQL CASE ti sia stata utile. Se è così, potrebbe piacere anche ai tuoi amici sviluppatori. Per favore condividilo sulle tue piattaforme di social media preferite. E facci sapere cosa ne pensi nella sezione Commenti.