Database
 sql >> Base de données >  >> RDS >> Database

SQL CASE :Connaître et éviter 3 problèmes moins connus

CAS SQL ? Du gâteau !

Vraiment ?

Pas avant de tomber sur 3 problèmes gênants qui peuvent entraîner des erreurs d'exécution et ralentir les performances.

Si vous essayez de parcourir les sous-titres pour voir quels sont les problèmes, je ne peux pas vous en vouloir. Les lecteurs, dont moi, sont impatients.

J'espère que vous connaissez déjà les bases de SQL CASE, donc je ne vous ennuierai pas avec de longues introductions. Approfondissons notre compréhension de ce qui se passe sous le capot.

1. SQL CASE n'évalue pas toujours séquentiellement

Les expressions de l'instruction Microsoft SQL CASE sont principalement évaluées de manière séquentielle ou de gauche à droite. C'est une autre histoire, cependant, lorsque vous l'utilisez avec des fonctions d'agrégation. Prenons un exemple :

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

Le code ci-dessus semble normal. Si je vous demande quel est le résultat de ces déclarations, vous direz probablement 1. L'inspection visuelle nous le dit parce que la @value est définie sur 0. Lorsque la @value est 0, le résultat est 1.

Mais ce n'est pas le cas ici. Jetez un œil au résultat réel de SQL Server Management Studio :

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

Mais pourquoi ?

Lorsque les expressions conditionnelles utilisent des fonctions d'agrégation telles que MAX() dans SQL CASE, elles sont évaluées en premier. Ainsi, MAX(1/@value) provoquera une erreur de division par zéro car @value est zéro.

Cette situation est plus gênante lorsqu'elle est cachée. Je l'expliquerai plus tard.

2. L'expression SQL CASE simple évalue plusieurs fois

Et alors ?

Bonne question. La vérité est qu'il n'y a aucun problème si vous utilisez des littéraux ou des expressions simples. Mais si vous utilisez des sous-requêtes comme expression conditionnelle, vous aurez une grosse surprise.

Avant d'essayer l'exemple ci-dessous, vous souhaiterez peut-être restaurer une copie de la base de données à partir d'ici. Nous l'utiliserons pour le reste des exemples.

Considérons maintenant cette requête très simple :


SELECT TOP 1 manufacturerID FROM SportsCars

C'est très simple, non ? Il renvoie 1 ligne avec 1 colonne de données. L'E/S STATISTIQUES révèle des lectures logiques minimales.

Note rapide  :Pour les non-initiés, avoir des lectures logiques plus élevées ralentit une requête. Lisez ceci pour plus de détails.

Le plan d'exécution révèle également un processus simple :

Maintenant, plaçons cette requête dans une expression CASE en tant que sous-requête :

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

Croisez les doigts car cela va anéantir les lectures logiques 4 fois.

Surprise! Comparé à la figure 1 avec seulement 2 lectures logiques, c'est 4 fois plus élevé. Ainsi, la requête est 4 fois plus lente. Comment cela a-t-il pu arriver ? Nous n'avons vu la sous-requête qu'une seule fois.

Mais ce n'est pas la fin de l'histoire. Consultez le plan d'exécution :

Nous voyons 4 instances des opérateurs Top et Index Scan dans la Figure 4. Si chaque Top et Index Scan consomment 2 lectures logiques, cela explique pourquoi les lectures logiques sont devenues 8 dans la Figure 3. Et puisque chaque Top et Index Scan ont un coût de 25 %. , cela nous indique également qu'ils sont identiques.

Mais cela ne s'arrête pas là. Les propriétés de l'opérateur Compute Scalar révèlent comment l'intégralité de l'instruction est traitée.

Nous voyons 4 expressions CASE WHEN provenant de l'opérateur Compute Scalar Defined Values. Il semble que notre expression CASE simple soit devenue une expression CASE recherchée comme celle-ci :

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;

Résumons. Il y avait 2 lectures logiques pour chaque opérateur Top et Index Scan. Ceci multiplié par 4 donne 8 lectures logiques. Nous avons également vu 4 expressions CASE WHEN dans l'opérateur Compute Scalar.

Au final, la sous-requête dans l'expression CASE simple a été évaluée 4 fois. Cela retardera votre requête.

Comment éviter plusieurs évaluations d'une sous-requête dans une expression CASE simple

Pour éviter un problème de performances tel que plusieurs instructions CASE dans SQL, nous devons réécrire la requête.

Tout d'abord, placez le résultat de la sous-requête dans une variable. Ensuite, utilisez cette variable dans la condition de l'expression SQL Server CASE simple, comme ceci :

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;

Est-ce une bonne solution ? Voyons les lectures logiques dans STATISTICS IO :

Nous voyons des lectures logiques inférieures à partir de la requête modifiée. Retirer la sous-requête et affecter le résultat à une variable est bien mieux. Qu'en est-il du plan d'exécution ? Voir ci-dessous.

L'opérateur Top and Index Scan n'est apparu qu'une seule fois, et non 4 fois. Merveilleux !

À emporter :n'utilisez pas de sous-requête comme condition dans l'expression CASE. Si vous avez besoin de récupérer une valeur, placez d'abord le résultat de la sous-requête dans une variable. Ensuite, utilisez cette variable dans l'expression CASE.

3. Ces 3 fonctions intégrées se transforment secrètement en SQL CASE

Il y a un secret, et l'instruction SQL Server CASE y est pour quelque chose. Si vous ne savez pas comment ces 3 fonctions se comportent, vous ne saurez pas que vous commettez une erreur que nous avons essayé d'éviter dans les points #1 et #2 plus tôt. Les voici :

  • IIF
  • COALESCER
  • CHOISIR

Examinons-les un par un.

IIF

J'ai utilisé Immediate IF, ou IIF, dans Visual Basic et Visual Basic pour Applications. Ceci est également équivalent à l'opérateur ternaire de C# : ?  :.

Cette fonction étant donnée une condition renverra 1 des 2 arguments basés sur le résultat de la condition. Et cette fonction est également disponible dans T-SQL. L'instruction CASE dans la clause WHERE peut être utilisée dans l'instruction SELECT

Mais ce n'est qu'une couche de sucre d'une expression CASE plus longue. Comment savons nous? Examinons un exemple.

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

Le résultat de cette requête est "Non". Cependant, consultez le plan d'exécution ainsi que les propriétés de Compute Scalar.

Étant donné que IIF est CAS QUAND, que pensez-vous qu'il se passera si vous exécutez quelque chose comme ça ?

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

Cela entraînera une erreur de division par zéro si @noOfPayments vaut 0. La même chose s'est produite au point n° 1 plus tôt.

Vous vous demandez peut-être ce qui cause cette erreur, car la requête ci-dessus donnera TRUE et devrait renvoyer 83333.33. Vérifiez à nouveau le point 1.

Ainsi, si vous êtes bloqué avec une erreur comme celle-ci lors de l'utilisation d'IIF, SQL CASE est le coupable.

COALESCER

COALESCE est également un raccourci d'une expression SQL CASE. Il évalue la liste de valeurs et renvoie la première valeur non nulle. Dans l'article précédent sur COALESCE, j'ai présenté un exemple qui évalue deux fois une sous-requête. Mais j'ai utilisé une autre méthode pour révéler le SQL CASE dans le plan d'exécution. Voici un autre exemple qui utilisera les mêmes techniques.

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

Voyons le plan d'exécution et les valeurs définies scalaires de calcul.

SQL CASE est correct. Le mot-clé COALESCE n'est nulle part dans la fenêtre Valeurs définies. Cela prouve le secret de cette fonction.

Mais ce n'est pas tout. Combien de fois avez-vous vu [Vehicles].[dbo].[Styles].[Style] dans la fenêtre Valeurs définies ? À DEUX REPRISES! Ceci est cohérent avec la documentation officielle de Microsoft. Imaginez si l'un des arguments de COALESCE est une sous-requête. Ensuite, doublez les lectures logiques et obtenez également l'exécution plus lente.

CHOISIR

Enfin, CHOISISSEZ. Ceci est similaire à la fonction MS Access CHOOSE. Il renvoie 1 valeur à partir d'une liste de valeurs basée sur une position d'index. Il agit également comme un index dans un tableau.

Voyons si nous pouvons creuser la transformation en un SQL CASE avec un exemple. Découvrez le code ci-dessous :

;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

Il y a notre exemple CHOISIR. Maintenant, vérifions le plan d'exécution et les valeurs définies scalaires de calcul :

Voyez-vous le mot-clé CHOISIR dans la fenêtre Valeurs définies de la figure 10 ? Que diriez-vous de CAS QUAND ?

Comme les exemples précédents, cette fonction CHOOSE n'est qu'un édulcorant pour une expression CASE plus longue. Et comme la requête comporte 2 éléments pour CHOOSE, les mots-clés CASE WHEN sont apparus deux fois. Voir la fenêtre Valeurs définies entourée d'un encadré rouge.

Cependant, nous avons plusieurs CASE WHEN en SQL ici. C'est à cause de l'expression CASE dans la requête interne du CTE. Si vous regardez attentivement, cette partie de la requête interne apparaît également deux fois.

À emporter

Maintenant que les secrets sont dévoilés, qu'avons-nous appris ?

  1. SQL CASE se comporte différemment lorsque des fonctions d'agrégation sont utilisées. Soyez prudent lorsque vous transmettez des arguments à des fonctions d'agrégation telles que MIN, MAX ou COUNT.
  2. Une simple expression CASE sera évaluée plusieurs fois. Notez-le et évitez de passer une sous-requête. Bien qu'il soit syntaxiquement correct, il fonctionnera mal.
  3. IIF, CHOOSE et COALESCE ont de sales secrets. Gardez cela à l'esprit avant de transmettre des valeurs à ces fonctions. Il se transformera en un SQL CASE. Selon les valeurs, vous provoquez soit une erreur, soit une baisse des performances.

J'espère que cette approche différente de SQL CASE vous a été utile. Si c'est le cas, vos amis développeurs l'apprécieront peut-être aussi. Veuillez le partager sur vos plateformes de médias sociaux préférées. Et dites-nous ce que vous en pensez dans la section Commentaires.