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

10 pièges SP_EXECUTESQL à éviter pour un meilleur SQL dynamique

Savez-vous à quel point un outil comme le SQL dynamique peut être puissant ? Utilisez-le dans le mauvais sens et vous pouvez permettre à quelqu'un de prendre en charge votre base de données. De plus, il pourrait y avoir trop de complexité. Cet article vise à présenter les pièges lors de l'utilisation de SP_EXECUTESQL et propose les 10 pièges les plus courants à éviter.

SP_EXECUTESQL est l'un des moyens d'exécuter des commandes SQL intégrées dans une chaîne. Vous construisez cette chaîne dynamiquement via le code. C'est pourquoi nous appelons ce SQL dynamique. Outre une série d'instructions, vous pouvez également lui transmettre une liste de paramètres et de valeurs. En fait, ces paramètres et valeurs diffèrent de la commande EXEC. EXEC n'accepte pas les paramètres pour le SQL dynamique. Pourtant, vous exécutez SP_EXECUTESQL en utilisant EXEC !

Pour un débutant en SQL dynamique, voici comment l'invoquer.

EXEC sp_executesql <command string>[, <input or output parameters list>, <parameter value1>, <parameter value n>]

Vous formez la chaîne de commandes qui incluent des instructions SQL valides. Vous pouvez éventuellement transmettre une liste de paramètres d'entrée ou de sortie et leurs types de données. Et enfin, vous passez une liste de valeurs séparées par des virgules. Si vous transmettez des paramètres, vous devez transmettre des valeurs. Plus tard, vous en verrez des exemples justes et faux au fur et à mesure de votre lecture.

Utiliser SP_EXECUTESQL lorsque vous n'en avez pas besoin

C'est exact. Si vous n'en avez pas besoin, ne l'utilisez pas. Si cela devient les 10 commandements de SP_EXECUTESQL, c'est le premier. C'est parce que cette procédure système peut être facilement abusée. Mais comment le savez-vous ?

Répondez à ceci :y a-t-il un problème si la commande dans votre SQL dynamique devient statique ? Si vous n'avez rien à dire sur ce point, alors vous n'en avez pas besoin. Voir l'exemple.

DECLARE @sql NVARCHAR(100) = N'SELECT ProductID, Name FROM Production.Product ' +
			      'WHERE ProductID = @ProductID';
DECLARE @paramsList NVARCHAR(100) = N'@ProductID INT';
DECLARE @param1Value INT = 1;

EXEC sp_executesql @sql, @paramsList, @param1Value
GO

Comme vous pouvez le voir, l'utilisation de SP_EXECUTESQL est complète avec une chaîne de commande, un paramètre et une valeur. Mais faut-il que ce soit ainsi ? Sûrement pas. C'est parfaitement bien d'avoir ceci :

DECLARE @productID INT = 1;

SELECT ProductID, Name
FROM Production.Product
WHERE ProductID = @productID;

Mais je pourrais être gêné quand vous voyez les exemples plus loin dans l'article. Parce que je vais contredire ce que j'affirme dans ce tout premier point. Vous verrez de courtes instructions SQL dynamiques qui sont meilleures que statiques. Alors, soyez indulgent avec moi car les exemples ne feront que prouver les points soulignés ici. Pour le reste des exemples, imaginez pendant un moment que vous regardez le code destiné au SQL dynamique.

Objets et variables hors champ

L'exécution d'une série de commandes SQL dans une chaîne à l'aide de SP_EXECUTESQL revient à créer une procédure stockée sans nom et à l'exécuter. Sachant cela, les objets tels que les tables temporaires et les variables en dehors de la chaîne de commande seront hors de portée. Pour cette raison, des erreurs d'exécution se produiront.

Lors de l'utilisation de variables SQL

Vérifiez ceci.

DECLARE @extraText VARCHAR(10) = 'Name is '; -- note this variable
DECLARE @sql NVARCHAR(100) = N'SELECT @extraText + FirstName + SPACE(1) + LastName
                               FROM Person.Person WHERE BusinessEntityID = @BusinessEntityID';
DECLARE @paramList NVARCHAR(100) = N'@BusinessEntityID INT';
DECLARE @param1Value INT = 1;

EXEC sp_executesql @sql, @paramList, @BusinessEntityId = @param1Value;
GO

La variable @extraText est invisible aux commandes exécutées. Au lieu de cela, une chaîne littérale ou la variable déclarée à l'intérieur de la chaîne SQL dynamique est bien meilleure. Quoi qu'il en soit, le résultat est :

Avez-vous vu cette erreur dans la figure 1 ? Si vous devez transmettre une valeur à l'intérieur de la chaîne SQL dynamique, ajoutez un autre paramètre.

Lors de l'utilisation de tables temporaires

Les tables temporaires dans SQL Server existent également dans le cadre d'un module. Alors, que pensez-vous de ce code ?

DECLARE @sql NVARCHAR(200) = N'SELECT BusinessEntityID, LastName, FirstName, MiddleName
                               INTO #TempNames
                               FROM Person.Person
                               WHERE BusinessEntityID BETWEEN 1 and 100';

EXEC sp_executesql @sql;
EXEC sp_executesql N'SELECT * FROM #TempNames'
GO

Le code ci-dessus exécute successivement 2 procédures stockées. La table temporaire créée à partir du premier SQL dynamique ne sera pas accessible au second. En conséquence, vous obtiendrez un nom d'objet invalide #TempNames erreur.

Erreur SQL Server QUOTENAME

Voici un autre exemple qui semblera correct à première vue mais qui provoquera une erreur de nom d'objet non valide. Voir le code ci-dessous.

DECLARE @sql NVARCHAR(100) = N'SELECT * FROM @Table';
DECLARE @tableName NVARCHAR(20) = 'Person.Person';

SET @sql = REPLACE(@sql,'@Table',QUOTENAME(@tableName));

PRINT @sql;
EXEC sp_executesql @sql;
GO

Pour être valide pour SELECT, entourez le schéma et le tableau de crochets comme ceci :[Schema].[Table] . Ou ne les entourez pas du tout (sauf si le nom de la table comprend un ou plusieurs espaces). Dans l'exemple ci-dessus, aucun crochet n'a été utilisé pour le tableau et le schéma. Au lieu d'utiliser @Table en tant que paramètre, il est devenu un espace réservé pour REPLACE. Le QUOTENAME a été utilisé.

QUOTENAME inclut une chaîne avec un délimiteur. C'est également utile pour traiter les guillemets simples dans la chaîne SQL dynamique. Le crochet est le délimiteur par défaut. Donc, dans l'exemple ci-dessus, que pensez-vous que QUOTENAME a fait ? Vérifiez la figure 2 ci-dessous.

L'instruction SQL PRINT nous a aidés à déboguer le problème en imprimant la chaîne SQL dynamique. Maintenant, nous connaissons le problème. Quelle est la meilleure façon de résoudre ce problème ? Une solution est le code ci-dessous :

DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) FROM @Table';
DECLARE @tableName NVARCHAR(20) = 'Person.Person';

SET @sql = REPLACE(@sql,'@Table',QUOTENAME(PARSENAME(@tableName,2)) + '.'
                               + QUOTENAME(PARSENAME(@tableName,1)));
PRINT @sql;
EXEC sp_executesql @sql;
GO

Expliquons cela.

D'abord, @Table est utilisé comme espace réservé pour REMPLACER. Il recherchera l'occurrence de @Table et remplacez-le par les valeurs correctes. Pourquoi? S'il est utilisé comme paramètre, une erreur se produira. C'est aussi la raison d'utiliser REPLACE.

Ensuite, nous avons utilisé PARSENAME. La chaîne que nous avons transmise à cette fonction la séparera en schéma et table. PARSENAME(@tableName,2) obtiendra le schéma. Tandis que PARSENAME(@tableName,1) obtiendra la table.

Enfin, QUOTENAME inclura les noms de schéma et de table séparément une fois PARSENAME terminé. Le résultat est [Personne].[Personne] . Maintenant, c'est valide.

Cependant, une meilleure façon de nettoyer la chaîne SQL dynamique avec les noms d'objets sera présentée plus tard.

Exécuter SP_EXECUTESQL avec une instruction NULL

Il y a des jours où vous êtes contrarié ou découragé. Des erreurs peuvent être commises en cours de route. Ajoutez ensuite une longue chaîne SQL dynamique au mélange et aux valeurs NULL. Et le résultat ?

Rien.

SP_EXECUTESQL vous donnera un résultat vide. Comment? En concaténant un NULL par erreur. Prenons l'exemple ci-dessous :

DECLARE @crlf NCHAR(2);
DECLARE @sql NVARCHAR(200) = N'SELECT' + @crlf +
	' p.Name AS Product' + @crlf +
	',v.Name AS Vendor' + @crlf +
	',v.AccountNumber' + @crlf +
	',p.ListPrice' + @crlf +
	'FROM Purchasing.ProductVendor pv' + @crlf +
	'INNER JOIN Production.Product p ON pv.ProductID = p.ProductID' + @crlf +
	'INNER JOIN Purchasing.Vendor v ON pv.BusinessEntityID = v.BusinessEntityID' + @crlf +
	'WHERE pv.BusinessEntityID = @BusinessEntityID';
DECLARE @paramsList NVARCHAR(100) = N'@BusinessEntityID INT';
DECLARE @BusinessEntityID INT = 1500;

PRINT @sql;
EXEC sp_executesql @sql, @paramsList, @BusinessEntityID
GO

Au début, le code semble bon. Pourtant, les yeux d'aigle parmi nous remarqueront le @crlf variable. Sa valeur? La variable n'a pas été initialisée. Donc, c'est NULL.

Mais à quoi sert cette variable ? Dans une section ultérieure, vous saurez à quel point il est important de formater et de déboguer. Pour l'instant, concentrons-nous sur le point en question.

Tout d'abord, la concaténation d'une variable NULL à la chaîne SQL dynamique donnera NULL. Ensuite, PRINT imprimera en blanc. Enfin, SP_EXECUTESQL fonctionnera correctement avec la chaîne SQL dynamique NULL. Mais cela ne renvoie rien.

Les NULL peuvent nous hypnotiser lors d'une journée déjà mauvaise. Faites une courte pause. Relaxer. Revenez ensuite avec un esprit plus clair.

Valeurs des paramètres d'intégration

Désordonné.

C'est à cela que ressembleront les valeurs intégrées à la chaîne SQL dynamique. Il y aura beaucoup de guillemets simples pour les chaînes et les dates. Si vous ne faites pas attention, les O'Briens et O'Neils provoqueront également des erreurs. Et puisque le SQL dynamique est une chaîne, vous devez CONVERT ou CAST les valeurs en chaîne. Voici un exemple.

DECLARE @shipDate DATETIME = '06/11/2011';
 DECLARE @productID INT = 750;
 DECLARE @sql NVARCHAR(1000);
 SET @sql = N'SELECT
  soh.ShipDate
 ,sod.ProductID
 ,SUM(sod.OrderQty) AS TotalQty
 ,SUM(sod.LineTotal) AS LineTotal
 FROM Sales.SalesOrderHeader soh
 INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
 WHERE soh.ShipDate BETWEEN ' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + '''' + ' AND DATEADD(MONTH,1,' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + ''') ' +
 'AND sod.ProductID = ' + CAST(@productID AS VARCHAR(8)) +
 ' GROUP BY soh.ShipDate, sod.ProductID' +
 ' ORDER BY sod.ProductID';
 
 PRINT @sql;
 EXEC sp_executesql @sql;

J'ai vu des cordes dynamiques plus désordonnées que celle-ci. Remarquez les guillemets simples, CONVERT et CAST. Si des paramètres étaient utilisés, cela pourrait mieux paraître. Vous pouvez le voir ci-dessous

DECLARE @shipDate DATETIME = '06/11/2011';
 DECLARE @productID INT = 750;
 DECLARE @sql NVARCHAR(1000);
 DECLARE @paramList NVARCHAR(500) = N'@shipDate DATETIME, @productID INT';
 SET @sql = N'SELECT
  soh.ShipDate
 ,sod.ProductID
 ,SUM(sod.OrderQty) AS TotalQty
 ,SUM(sod.LineTotal) AS LineTotal
 FROM Sales.SalesOrderHeader soh
 INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
 WHERE soh.ShipDate BETWEEN @shipDate AND DATEADD(MONTH,1,@shipDate)
 AND sod.ProductID = @productID
  GROUP BY soh.ShipDate, sod.ProductID
  ORDER BY sod.ProductID';

PRINT @sql;
EXEC sp_executesql @sql, @paramList, @shipDate, @productID
GO

Voir? Moins de guillemets simples, pas de CONVERT et CAST, et plus propre aussi.

Mais les valeurs en ligne ont un effet secondaire encore plus dangereux.

Injection SQL

Si nous vivions dans un monde où tout le monde était bon, l'injection SQL ne serait jamais envisagée. Mais ce n'est pas le cas. Quelqu'un pourrait injecter du code SQL malveillant dans le vôtre. Comment cela peut-il arriver ?

Voici le scénario que nous allons utiliser dans notre exemple :

  • Les valeurs sont fusionnées à la chaîne SQL dynamique comme dans notre exemple précédent. Aucun paramètre.
  • La chaîne SQL dynamique peut atteindre 2 Go en utilisant NVARCHAR(MAX). Beaucoup d'espace pour injecter du code malveillant.
  • Pire, la chaîne SQL dynamique est exécutée avec des autorisations élevées.
  • L'instance SQL Server accepte l'authentification SQL.

Est-ce trop ? Pour les systèmes gérés par un seul homme, cela peut arriver. Personne ne le surveille de toute façon. Pour les grandes entreprises, un service informatique laxiste en matière de sécurité existe parfois.

Est-ce encore une menace aujourd'hui ? C'est le cas, selon le fournisseur de services cloud Akamai dans son rapport State of the Internet/Security. Entre novembre 2017 et mars 2019, l'injection SQL représente près des deux tiers de toutes les attaques d'applications Web. C'est la plus élevée de toutes les menaces examinées. Très mauvais.

Voulez-vous le voir par vous-même ?

Pratique d'injection SQL : mauvais exemple

Faisons une injection SQL dans cet exemple. Vous pouvez essayer cela dans votre propre AdventureWorks base de données. Mais assurez-vous que l'authentification SQL est autorisée et que vous l'exécutez avec des autorisations élevées.

DECLARE @lastName NVARCHAR(MAX) = 'Mu';
DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);

DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
' p.LastName ' + @crlf +
',p.FirstName ' + @crlf +
',a.AddressLine1 ' + @crlf +
',a.AddressLine2 ' + @crlf +
',a.City ' + @crlf +
'FROM Person.Person p ' + @crlf +
'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
'WHERE p.LastName = ' + NCHAR(39) + @lastName + NCHAR(39) + ' ' + @crlf +
'AND p.FirstName = '  + NCHAR(39) + @firstName + NCHAR(39);

-- SELECT @sql;	-- uncomment if you want to see what's in @sql					
EXEC sp_executesql @sql;
GO

Le code ci-dessus ne représente pas le code réel d'une entreprise existante. Il n'est même pas appelable par une application. Mais cela illustre l'action mauvaise. Alors, qu'est-ce qu'on a ici ?

Tout d'abord, le code injecté créera un compte SQL qui ressemble à sa , mais ce n'est pas. Et comme sa , cela a sysadmin autorisations. Finalement, cela sera utilisé pour accéder à la base de données à tout moment avec tous les privilèges. Tout est possible une fois que cela est fait :voler, supprimer, falsifier des données d'entreprise, etc.

Ce code fonctionnera-t-il ? Définitivement! Et une fois que c'est le cas, le super compte sera créé silencieusement. Et, bien sûr, l'adresse de Zheng Mu apparaîtra dans le jeu de résultats. Tout le reste est normal. Shady, tu ne penses pas ?

Après avoir exécuté le code ci-dessus, essayez également celui-ci :

SELECT IS_SRVROLEMEMBER('sysadmin','sà')

S'il renvoie 1, il est dedans, qui que ce soit est. Vous pouvez également le vérifier dans les connexions de sécurité de votre serveur SQL dans SQL Server Management Studio.

Alors, qu'avez-vous obtenu ?

Effrayant, n'est-ce pas? (Si c'est réel, ça l'est.)

Pratique d'injection SQL :bon exemple

Maintenant, changeons un peu le code en utilisant des paramètres. Les autres conditions sont toujours les mêmes.

DECLARE @lastName NVARCHAR(MAX) = 'Mu';
DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);

DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
' p.LastName ' + @crlf +
',p.FirstName ' + @crlf +
',a.AddressLine1 ' + @crlf +
',a.AddressLine2 ' + @crlf +
',a.City ' + @crlf +
'FROM Person.Person p ' + @crlf +
'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
'WHERE p.LastName = @lastName' + @crlf +
'AND p.FirstName = @firstName';

DECLARE @paramList NVARCHAR(300) = N'@lastName NVARCHAR(50), @firstName NVARCHAR(50)';

-- SELECT @sql;	-- uncomment if you want to see what's in @sql
EXEC sp_executesql @sql, @paramList, @lastName, @firstName;
GO

Il y a 2 différences dans le résultat par rapport au premier exemple.

  • Tout d'abord, l'adresse de Zheng Mu n'apparaîtra pas. Le jeu de résultats est vide.
  • Ensuite, le compte renégat n'est pas créé. L'utilisation de IS_SRVROLEMEMBER renverra NULL.

Que s'est-il passé ?

Puisque des paramètres sont utilisés, la valeur de @firstName est « Zheng » ; CREATE LOGIN avec PASSWORD=”12345”; ALT' . Ceci est considéré comme une valeur littérale et tronqué à 50 caractères seulement. Vérifiez le paramètre du prénom dans le code ci-dessus. C'est NVARCHAR(50). C'est pourquoi le jeu de résultats est vide. Aucune personne avec un prénom comme ça n'est dans la base de données.

Ceci n'est qu'un exemple d'injection SQL et une façon de l'éviter. Il y a plus de choses à faire. Mais j'espère avoir clairement expliqué pourquoi les valeurs inline en SQL dynamique sont mauvaises.

Renifleur de paramètres

Avez-vous rencontré une procédure stockée lente à partir d'une application, mais lorsque vous avez essayé de l'exécuter dans SSMS, elle est devenue rapide ? C'est déconcertant car vous avez utilisé les valeurs exactes des paramètres utilisés dans l'application.

C'est le reniflage de paramètres en action. SQL Server crée un plan d'exécution la première fois que la procédure stockée est exécutée ou recompilée. Ensuite, réutilisez le plan pour la prochaine exécution. Cela sonne bien car SQL Server n'a pas besoin de recréer le plan à chaque fois. Mais il arrive parfois qu'une valeur de paramètre différente nécessite un plan différent pour s'exécuter rapidement.

Voici une démonstration utilisant SP_EXECUTESQL et un SQL statique simple.

DECLARE @sql NVARCHAR(150) = N'SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID';
DECLARE @paramList NVARCHAR(100) = N'@ProductSubcategoryID INT';
DECLARE @ProductSubcategoryID INT = 23;

EXEC sp_executesql @sql, @paramList, @ProductSubcategoryID

Celui-ci est très simple. Vérifiez le plan d'exécution de la figure 3.

Essayons maintenant la même requête en utilisant du SQL statique.

DECLARE @ProductSubcategoryID INT = 23;
SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID

Consultez la figure 4, puis comparez-la à la figure 3.

Dans la figure 3, Recherche d'index et boucle imbriquée sont utilisés. Mais dans la figure 4, il s'agit d'un balayage d'index en cluster . Bien qu'il n'y ait pas de pénalité de performance perceptible à ce stade, cela montre que le reniflage de paramètres n'est pas seulement une imagination.

Cela peut être très frustrant une fois que la requête devient lente. Vous pourriez finir par utiliser des techniques telles que la recompilation ou l'utilisation d'indicateurs de requête pour l'éviter. Chacun de ces éléments présente des inconvénients.

Chaîne SQL dynamique non formatée dans SP_EXECUTESQL

Qu'est-ce qui peut mal tourner avec ce code ?

DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) AS ProductCount' +
                              'FROM Production.Product';
PRINT @sql;
EXEC sp_executesql @sql;

C'est court et simple. Mais vérifiez la figure 5 ci-dessous.

Des erreurs se produisent si vous ne vous souciez pas d'un seul espace entre les mots-clés et les objets lors de la formation de la chaîne SQL dynamique. Comme dans la figure 5, où le ProductCount l'alias de colonne et le mot-clé FROM n'ont pas d'espace entre eux. Cela devient déroutant une fois qu'une partie d'une chaîne passe à la ligne de code suivante. Cela vous fait penser que la syntaxe est correcte.

Notez également que la chaîne a utilisé 2 lignes dans la fenêtre de code, mais la sortie de PRINT affiche 1 ligne. Imaginez s'il s'agit d'une chaîne de commande très, très longue. Il est difficile de trouver le problème tant que vous n'avez pas formaté correctement la chaîne à partir de l'onglet Messages.

Pour résoudre ce problème, ajoutez un retour chariot et un saut de ligne. Vous remarquez probablement une variable @crlf des exemples précédents. Formater votre chaîne SQL dynamique avec un espace et une nouvelle ligne rendra la chaîne SQL dynamique plus lisible. C'est également idéal pour le débogage.

Considérez une instruction SELECT avec JOIN. Il nécessite plusieurs lignes de code comme dans l'exemple ci-dessous.

DECLARE @sql NVARCHAR(400)
DECLARE @shipDate DATETIME = '06/11/2011';
DECLARE @paramList NVARCHAR(100) = N'@shipDate DATETIME';
DECLARE @crlf NCHAR(2) = NCHAR(13) + NCHAR(10);

set @sql = N'SELECT ' + @crlf +
 'soh.ShipDate ' + @crlf +
 ',sod.ProductID ' + @crlf +
 ',SUM(sod.OrderQty) AS TotalQty ' + @crlf +
 ',SUM(sod.LineTotal) AS LineTotal ' + @crlf +
 'FROM Sales.SalesOrderHeader soh ' + @crlf +
 'INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID ' + @crlf +
 'WHERE soh.ShipDate = @shipDate' + @crlf +
 'GROUP BY soh.ShipDate, sod.ProductID ' + @crlf +
 'ORDER BY sod.ProductID';

 PRINT @sql;
 EXEC sp_executesql @sql,@paramList,@shipDate
 GO

Pour formater la chaîne, le @crlf est définie sur NCHAR(13), un retour chariot, et NCHAR(10), un saut de ligne. Il est concaténé à chaque ligne pour rompre une longue chaîne d'instruction SELECT. Pour voir le résultat dans l'onglet Messages, nous utilisons PRINT. Vérifiez la sortie dans la figure 6 ci-dessous.

La façon dont vous formez la chaîne SQL dynamique dépend de vous. Tout ce qui vous convient pour le rendre clair, lisible et facile à déboguer le moment venu.

Noms d'objets non nettoyés

Avez-vous besoin de définir dynamiquement des noms de table, de vue ou de base de données pour une raison quelconque ? Ensuite, vous devez "désinfecter" ou valider ces noms d'objets pour éviter les erreurs.

Dans notre exemple, nous utiliserons un nom de table, bien que le principe de validation puisse également s'appliquer aux vues. La façon dont vous le gérerez ensuite sera différente.

Auparavant, nous utilisions PARSENAME pour séparer le nom du schéma du nom de la table. Il peut également être utilisé si la chaîne a un nom de serveur et de base de données. Mais dans cet exemple, nous n'utiliserons que des noms de schéma et de table. Je laisse le reste à vos brillants esprits. Cela fonctionnera peu importe si vous nommez vos tables avec ou sans espaces. Les espaces sur les noms de table ou de vue sont valides. Donc, cela fonctionne pour dbo.MyFoodCravings ou [dbo].[Mes envies alimentaires] .

EXEMPLE

Créons un tableau.

CREATE TABLE [dbo].[My Favorite Bikes]
(
	id INT NOT NULL,
	BikeName VARCHAR(50)
)
GO

Ensuite, nous créons un script qui utilisera SP_EXECUTESQL. Cela exécutera une instruction DELETE générique pour toute table dotée d'une clé à 1 colonne. La première chose à faire est d'analyser le nom complet de l'objet.

DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);

De cette façon, nous séparons le schéma de la table. Pour valider davantage, nous utilisons OBJECT_ID. Si ce n'est pas NULL, alors c'est valide.

IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
BEGIN
	PRINT @object + ' is valid!'
	-- do the rest of your stuff here
END
ELSE
BEGIN
        PRINT 'Invalid object name ' + @object
	-- if you need to do anything else, insert it here
END

Notez également que nous avons utilisé QUOTENAME. Cela garantira que les noms de table contenant des espaces ne déclencheront pas d'erreur en les mettant entre crochets.

Mais qu'en est-il de la validation de la colonne clé ? Vous pouvez vérifier l'existence de la colonne de la table cible dans sys.columns .

IF (SELECT COUNT(*) FROM sys.columns
	    WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
		  AND [name] = @idKey) > 0
BEGIN
     -- add miscellaneous code here, if needed
     EXEC sp_executesql @sql, @paramsList, @id
END
ELSE
BEGIN
     PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
     -- if you need to do anything else, insert it here
END

Maintenant, voici le script complet de ce que nous voulons accomplir.

DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);
DECLARE @isDebug BIT = 1;
DECLARE @idKey NVARCHAR(128) = N'id';

DECLARE @sql NVARCHAR(200) = N'DELETE FROM @object WHERE @idKey = @id';
DECLARE @id INT = 0;
DECLARE @paramList NVARCHAR(100) = N'@id INT';

IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
BEGIN
   PRINT @object + ' is valid!'
   
   IF (SELECT COUNT(*) FROM sys.columns
       WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
         AND [name] = @idKey) > 0
   BEGIN
       SET @sql = REPLACE(@sql, '@object', QUOTENAME(@schemaName) + '.' +          
                  QUOTENAME(@tableName));
       SET @sql = REPLACE(@sql, '@idkey', QUOTENAME(@idKey));
       IF @isDebug = 1
	   PRINT @sql;
       EXEC sp_executesql @sql, @paramList, @id
   END
   ELSE
       PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
END
ELSE
BEGIN
   PRINT 'Invalid object name ' + @object
   -- if you need to do anything else, insert it here
END
GO

Le résultat de ce script est dans la figure 7 ci-dessous.

Vous pouvez essayer cela avec d'autres tables. Changez simplement le @object , @idkey , et @id valeurs variables.

Aucune provision pour le débogage

Des erreurs peuvent arriver. Vous devez donc connaître la chaîne SQL dynamique générée pour trouver la cause première. Nous ne sommes pas des diseurs de bonne aventure ou des magiciens pour deviner la forme de la chaîne SQL dynamique. Donc, vous avez besoin d'un indicateur de débogage.

Remarquez dans la figure 7 précédente que la chaîne SQL dynamique est imprimée dans l'onglet Messages de SSMS. Nous avons ajouté un @isDebug variable BIT et réglez-la sur 1 dans le code. Lorsque la valeur est 1, la chaîne SQL dynamique s'imprime. C'est bien si vous avez besoin de déboguer un script ou une procédure stockée comme celui-ci. Remettez-le simplement à zéro lorsque vous avez terminé le débogage. S'il s'agit d'une procédure stockée, faites de cet indicateur un paramètre facultatif avec une valeur par défaut de zéro.

Pour voir la chaîne SQL dynamique, vous pouvez utiliser 2 méthodes possibles.

  • Utilisez PRINT si la chaîne est inférieure ou égale à 8 000 caractères.
  • Ou utilisez SELECT si la chaîne comporte plus de 8 000 caractères.

SQL dynamique peu performant utilisé dans SP_EXECUTESQL

SP_EXECUTESQL peut être lent si vous lui affectez une requête à exécution lente. Période. Cela n'implique pas encore de problèmes avec le reniflage des paramètres.

Alors, commencez statique avec le code que vous souhaitez exécuter dynamiquement. Ensuite, vérifiez le Plan d'exécution et les E/S STATISTIQUES. Vérifiez s'il manque des index que vous devez créer. Accordez-le tôt.

L'essentiel dans SP_EXECUTESQL

Utiliser SP_EXECUTESQL, c'est comme manier une arme puissante. Mais celui qui le manie doit être doué pour ça. Bien que ce ne soit pas non plus sorcier. Si vous êtes un débutant aujourd'hui, cela peut devenir logique avec le temps et la pratique.

Cette liste de pièges n'est pas complète. Mais il couvre les plus communs. Si vous avez besoin de plus d'informations, consultez ces liens :

  • La malédiction et les bénédictions du SQL dynamique, par Erland Sommarskog
  • SP_EXECUTESQL (Transact-SQL), par Microsoft

Comme ça? Alors partagez-le sur vos plateformes de médias sociaux préférées. Vous pouvez également partager avec nous vos conseils éprouvés dans la section Commentaires.