sql >> Database >  >> RDS >> Sqlserver

10 SP_EXECUTESQL Gotcha's om te vermijden voor een betere dynamische SQL

Weet u hoe krachtig een tool als dynamische SQL kan zijn? Gebruik het op de verkeerde manier en u kunt iemand uw database laten overnemen. Bovendien kan er te veel complexiteit zijn. Dit artikel is bedoeld om de valkuilen te introduceren bij het gebruik van SP_EXECUTESQL en biedt 10 meest voorkomende problemen om te vermijden.

SP_EXECUTESQL is een van de manieren waarop u SQL-opdrachten kunt uitvoeren die zijn ingesloten in een tekenreeks. Je bouwt deze string dynamisch door de code heen. Daarom noemen we dit dynamische SQL. Afgezien van een reeks instructies, kunt u er ook een lijst met parameters en waarden in opnemen. In feite verschillen deze parameters en waarden van het EXEC-commando. EXEC accepteert geen parameters voor dynamische SQL. Toch voer je SP_EXECUTESQL uit met EXEC!

Voor een beginner op het gebied van dynamische SQL, kun je dit als volgt aanroepen.

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

U vormt de reeks opdrachten die geldige SQL-instructies bevatten. Optioneel kunt u een lijst met invoer- of uitvoerparameters en hun gegevenstypen doorgeven. En tot slot geef je een door komma's gescheiden lijst met waarden door. Als u parameters doorgeeft, moet u waarden doorgeven. Later zul je hier zowel goede als foute voorbeelden van zien terwijl je verder leest.

SP_EXECUTESQL gebruiken als je het niet nodig hebt

Dat klopt. Als je het niet nodig hebt, gebruik het dan niet. Als dit de 10 geboden van SP_EXECUTESQL worden, is dit het eerste. Het is omdat deze systeemprocedure gemakkelijk kan worden misbruikt. Maar hoe weet je dat?

Beantwoord dit:Is er een probleem als de opdracht in uw dynamische SQL statisch wordt? Als je op dit punt niets te zeggen hebt, dan heb je het niet nodig. Zie het voorbeeld.

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

Zoals u kunt zien, is het gebruik van SP_EXECUTESQL compleet met een opdrachtreeks, parameter en waarde. Maar moet het zo? Natuurlijk niet. Het is prima om dit te hebben:

DECLARE @productID INT = 1;

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

Maar misschien schaam ik me als je de voorbeelden verderop in het artikel ziet. Omdat ik in dit allereerste punt zal tegenspreken wat ik beweer. U zult korte dynamische SQL-instructies zien die beter zijn dan statische. Dus wees geduldig, want de voorbeelden zullen de punten die hier worden uiteengezet alleen maar bewijzen. Voor de rest van de voorbeelden, doe even alsof je naar de code kijkt die bedoeld is voor dynamische SQL.

Objecten en variabelen die buiten het bereik vallen

Het uitvoeren van een reeks SQL-opdrachten in een tekenreeks met SP_EXECUTESQL is als het maken van een naamloze opgeslagen procedure en het uitvoeren ervan. Dit wetende, vallen objecten zoals tijdelijke tabellen en variabelen buiten de opdrachtreeks buiten het bereik. Hierdoor zullen er runtime-fouten optreden.

Bij gebruik van SQL-variabelen

Bekijk dit eens.

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

De variabele @extraText is onzichtbaar voor uitgevoerde opdrachten. In plaats daarvan is een letterlijke tekenreeks of de variabele gedeclareerd in de dynamische SQL-tekenreeks veel beter. Hoe dan ook, het resultaat is:

Heb je die fout in figuur 1 gezien? Als u een waarde binnen de dynamische SQL-tekenreeks moet doorgeven, voegt u een andere parameter toe.

Bij gebruik van tijdelijke tabellen

Tijdelijke tabellen in SQL Server bestaan ​​ook binnen het bereik van een module. Dus, wat vind je van deze 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

De bovenstaande code voert achtereenvolgens 2 opgeslagen procedures uit. De tijdelijke tabel die is gemaakt op basis van de eerste dynamische SQL, is niet toegankelijk voor de tweede. Als resultaat krijgt u een Ongeldige objectnaam #TempNames fout.

SQL Server QUOTENAME-fout

Hier is nog een voorbeeld dat er op het eerste gezicht goed uitziet, maar een ongeldige objectnaam-fout veroorzaakt. Zie de onderstaande code.

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

Om geldig te zijn voor SELECT, plaatst u het schema en de tabel als volgt tussen vierkante haken:[Schema].[Tabel] . Of sluit ze helemaal niet in (tenzij de tabelnaam een ​​of meer spaties bevat). In het bovenstaande voorbeeld zijn er geen vierkante haken gebruikt voor zowel de tabel als het schema. In plaats van @Table te gebruiken als parameter werd het een tijdelijke aanduiding voor REPLACE. De QUOTENAME werd gebruikt.

QUOTENAME omsluit een string met een scheidingsteken. Dit is ook goed voor het omgaan met enkele aanhalingstekens in de dynamische SQL-tekenreeks. De vierkante haak is het standaard scheidingsteken. Dus, in het bovenstaande voorbeeld, wat denk je dat QUOTENAME deed? Controleer Afbeelding 2 hieronder.

De SQL PRINT-instructie heeft ons geholpen het probleem op te lossen door de dynamische SQL-string af te drukken. Nu kennen we het probleem. Wat is de beste manier om dit op te lossen? Een oplossing is de onderstaande code:

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

Laten we dit uitleggen.

Ten eerste, @Table wordt gebruikt als een tijdelijke aanduiding voor REPLACE. Er wordt gezocht naar het voorkomen van @Table en vervang deze door de juiste waarden. Waarom? Als dit als parameter wordt gebruikt, treedt er een fout op. Dat is ook de reden om REPLACE te gebruiken.

Vervolgens gebruikten we PARSENAME. De tekenreeks die we aan deze functie hebben doorgegeven, scheidt deze in schema en tabel. PARSENAME(@tableName,2) krijgt het schema. Terwijl PARSENAME(@tableName,1) krijgt de tafel.

Ten slotte zal QUOTENAME het schema en de tabelnamen afzonderlijk omsluiten nadat PARSENAME is voltooid. Het resultaat is [Persoon].[Persoon] . Nu is het geldig.

Een betere manier om de dynamische SQL-string op te schonen met objectnamen zal later echter worden getoond.

SP_EXECUTESQL uitvoeren met een NULL-instructie

Er zijn dagen dat je boos of neerslachtig bent. Onderweg kunnen fouten gemaakt worden. Voeg vervolgens een lange dynamische SQL-tekenreeks toe aan de mix en NULL's. En het resultaat?

Niets.

SP_EXECUTESQL geeft je een leeg resultaat. Hoe? Door per ongeluk een NULL samen te voegen. Beschouw het onderstaande voorbeeld:

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

In eerste instantie ziet de code er goed uit. Toch zullen de adelaarsogen onder ons de @crlf . opmerken variabel. Zijn waarde? De variabele is niet geïnitialiseerd. Het is dus NULL.

Maar wat is het nut van die variabele? In een later gedeelte zult u weten hoe belangrijk het is voor formatteren en debuggen. Laten we ons voorlopig concentreren op het punt dat voorligt.

Ten eerste zal het samenvoegen van een NULL-variabele aan de dynamische SQL-tekenreeks resulteren in NULL. PRINT wordt vervolgens blanco afgedrukt. Ten slotte zal SP_EXECUTESQL prima werken met de NULL dynamische SQL-string. Maar het levert niets op.

NULL's kunnen ons betoveren op een toch al slechte dag. Neem een ​​korte pauze. Kom tot rust. Kom dan terug met een helderder hoofd.

Parameterwaarden invoegen

Rommelig.

Dat is hoe inlining-waarden naar dynamische SQL-tekenreeksen eruit zullen zien. Er zullen veel enkele aanhalingstekens zijn voor strings en datums. Als je niet oppast, zullen de O'Briens en O'Neils ook fouten veroorzaken. En aangezien dynamische SQL een string is, moet u de waarden CONVERTEREN of CAST naar string. Hier is een voorbeeld.

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;

Ik zag rommeligere dynamische snaren dan dit. Let op de enkele aanhalingstekens, CONVERT en CAST. Als er parameters zouden worden gebruikt, zou dit er beter uit kunnen zien. Je kunt het hieronder zien

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

Zie je wel? Minder enkele aanhalingstekens, geen CONVERT en CAST, en ook schoner.

Maar er is een nog gevaarlijker neveneffect van inline waarden.

SQL-injectie

Als we in een wereld zouden leven waar alle mensen goed waren, zou er nooit aan SQL-injectie worden gedacht. Maar dat is niet het geval. Iemand kan schadelijke SQL-code in de uwe injecteren. Hoe kan dit gebeuren?

Dit is het scenario dat we in ons voorbeeld gaan gebruiken:

  • Waarden worden samengevoegd met de dynamische SQL-string, zoals in ons eerder voorbeeld. Geen parameters.
  • De dynamische SQL-tekenreeks is maximaal 2 GB met NVARCHAR(MAX). Veel ruimte om kwaadaardige code te injecteren.
  • Erger nog, de dynamische SQL-string wordt uitgevoerd met verhoogde rechten.
  • De SQL Server-instantie accepteert SQL-verificatie.

Is dit te veel? Voor systemen die door één man worden beheerd, kan dit gebeuren. Niemand controleert hem toch. Voor grotere bedrijven bestaat er soms een IT-afdeling die laks is met beveiliging.

Is dit vandaag de dag nog steeds een bedreiging? Volgens cloudserviceprovider Akamai in hun State of the Internet/Security-rapport. Tussen november 2017 en maart 2019 vertegenwoordigt SQL-injectie bijna twee derde van alle aanvallen op webapplicaties. Dat is de hoogste van alle onderzochte bedreigingen. Heel slecht.

Wil je het zelf zien?

SQL-injectiepraktijk: Slecht voorbeeld

Laten we in dit voorbeeld wat SQL-injectie doen. Je kunt dit proberen in je eigen AdventureWorks databank. Maar zorg ervoor dat SQL-authenticatie is toegestaan, en dat je het uitvoert met verhoogde rechten.

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

De bovenstaande code vertegenwoordigt geen echte code van een bestaand bedrijf. Het is niet eens oproepbaar door een app. Maar dit illustreert de slechte daad. Dus, wat hebben we hier?

Eerst maakt de geïnjecteerde code een SQL-account aan dat eruitziet als sa , maar dat is het niet. En like sa , dit heeft sysadmin rechten. Uiteindelijk zal dit worden gebruikt om op elk moment toegang te krijgen tot de database met volledige privileges. Alles is mogelijk als dit eenmaal is gedaan:stelen, verwijderen, knoeien met bedrijfsgegevens, noem maar op.

Wordt deze code uitgevoerd? Definitief! En als dat eenmaal het geval is, wordt het superaccount in stilte aangemaakt. En natuurlijk verschijnt het adres van Zheng Mu in de resultatenset. Al het andere is normaal. Schaduwrijk, vind je niet?

Nadat je de bovenstaande code hebt uitgevoerd, probeer je deze ook uit te voeren:

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

Als het 1 retourneert, doet hij mee, wie dit ook is is. U kunt het ook controleren in de beveiligingsaanmeldingen van uw SQL Server in SQL Server Management Studio.

Dus, wat heb je gekregen?

Eng, niet? (Als dit echt is, is het dat ook.)

SQL-injectiepraktijk:goed voorbeeld

Laten we nu de code een beetje veranderen door parameters te gebruiken. De andere voorwaarden zijn nog steeds hetzelfde.

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

Er zijn 2 verschillen in het resultaat vergeleken met het eerste voorbeeld.

  • Eerst wordt het adres van Zheng Mu niet weergegeven. De resultatenset is leeg.
  • Het afvallige account wordt dan niet aangemaakt. Het gebruik van IS_SRVROLEMEMBER levert NULL op.

Wat is er gebeurd?

Aangezien parameters worden gebruikt, is de waarde van @firstName is ‘Zheng”; AANMELDEN sà MET WACHTWOORD=”12345”; ALT' . Dit wordt als een letterlijke waarde genomen en afgekapt tot slechts 50 tekens. Controleer de parameter voornaam in de bovenstaande code. Het is NVARCHAR(50). Daarom is de resultatenset leeg. Er is geen persoon met zo'n voornaam in de database.

Dit is slechts een voorbeeld van SQL-injectie en een manier om het te vermijden. Er komt meer bij kijken bij het daadwerkelijk doen. Maar ik hoop dat ik duidelijk heb gemaakt waarom inline-waarden in dynamische SQL slecht zijn.

Parameter snuiven

Heb je een langzaam lopende opgeslagen procedure van een app ervaren, maar toen je probeerde het in SSMS uit te voeren, werd het snel? Het is verbijsterend omdat je de exacte parameterwaarden hebt gebruikt die in de app worden gebruikt.

Dat is parameter snuiven in actie. SQL Server maakt een uitvoeringsplan wanneer de opgeslagen procedure voor het eerst wordt uitgevoerd of opnieuw wordt gecompileerd. Gebruik het plan vervolgens opnieuw voor de volgende run. Dat klinkt geweldig omdat SQL Server het plan niet elke keer opnieuw hoeft te maken. Maar er zijn tijden dat een andere parameterwaarde een ander plan nodig heeft om snel te kunnen werken.

Hier is een demonstratie met SP_EXECUTESQL en een eenvoudige statische SQL.

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

Deze is heel eenvoudig. Controleer het uitvoeringsplan in figuur 3.

Laten we nu dezelfde query proberen met statische SQL.

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

Bekijk figuur 4 en vergelijk het met figuur 3.

In figuur 3, Index zoeken en geneste lus worden gebruikt. Maar in figuur 4 is het een geclusterde indexscan . Hoewel er op dit moment geen waarneembare prestatievermindering is, laat dit zien dat het snuiven van parameters niet alleen maar fantasie is.

Dit kan erg frustrerend zijn als de query langzaam wordt. U kunt uiteindelijk technieken gebruiken zoals hercompileren of het gebruik van query-hints om dit te vermijden. Elk van deze heeft nadelen.

Ongeformatteerde dynamische SQL-tekenreeks in SP_EXECUTESQL

Wat kan er mis gaan met deze code?

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

Het is kort en eenvoudig. Maar controleer Afbeelding 5 hieronder.

Er treden fouten op als u een enkele spatie tussen trefwoorden en objecten niet erg vindt bij het vormen van de dynamische SQL-tekenreeks. Zoals in figuur 5, waar de ProductCount kolomalias en het trefwoord FROM hebben geen spatie ertussen. Het wordt verwarrend zodra een deel van een tekenreeks naar de volgende regel code stroomt. Het laat je denken dat de syntaxis correct is.

Merk ook op dat de string 2 regels gebruikte in het codevenster, maar de uitvoer van PRINT toont 1 regel. Stel je voor dat dit een heel, heel lange opdrachtreeks is. Het is moeilijk om het probleem te vinden totdat u de tekenreeks op de juiste manier opmaakt vanaf het tabblad Berichten.

Om dit probleem op te lossen, voegt u een regelterugloop en regelinvoer toe. Je ziet waarschijnlijk een variabele @crlf uit de vorige voorbeelden. Als u uw dynamische SQL-tekenreeks opmaakt met spatie en een nieuwe regel, wordt de dynamische SQL-tekenreeks leesbaarder. Dit is ook geweldig om te debuggen.

Overweeg een SELECT-instructie met JOIN. Het heeft verschillende regels code nodig, zoals in het onderstaande voorbeeld.

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

Om de tekenreeks op te maken, gebruikt de @crlf variabele is ingesteld op NCHAR(13), een regelterugloop en NCHAR(10), een regelinvoer. Het wordt aan elke regel gekoppeld om een ​​lange reeks SELECT-instructies te verbreken. Om het resultaat in het tabblad Berichten te zien, gebruiken we PRINT. Controleer de uitvoer in Afbeelding 6 hieronder.

Hoe u de dynamische SQL-string vormt, is aan u. Wat u maar wilt om het duidelijk, leesbaar en gemakkelijk te debuggen te maken wanneer de tijd daar is.

Niet-opgeschoonde objectnamen

Moet u om welke reden dan ook dynamisch tabel-, weergave- of databasenamen instellen? Vervolgens moet u deze objectnamen "opschonen" of valideren om fouten te voorkomen.

In ons voorbeeld gebruiken we een tabelnaam, hoewel het validatieprincipe ook van toepassing kan zijn op views. Hoe je er vervolgens mee omgaat, zal anders zijn.

Eerder gebruikten we PARSENAME om de schemanaam van de tabelnaam te scheiden. Het kan ook worden gebruikt als de string een server- en databasenaam heeft. Maar in dit voorbeeld gebruiken we alleen schema- en tabelnamen. Ik laat de rest over aan je briljante geest. Dit werkt ongeacht of u uw tabellen een naam geeft met of zonder spaties. Spaties op tafel- of weergavenamen zijn geldig. Het werkt dus voor dbo.MyFoodCravings of [dbo].[Mijn verlangen naar eten] .

VOORBEELD

Laten we een tabel maken.

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

Vervolgens maken we een script dat SP_EXECUTESQL zal gebruiken. Hiermee wordt een algemene DELETE-instructie uitgevoerd voor elke tabel met een sleutel met 1 kolom. Het eerste dat u moet doen, is de volledige objectnaam ontleden.

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

Op deze manier scheiden we het schema van de tabel. Om verder te valideren, gebruiken we OBJECT_ID. Als het niet NULL is, is het geldig.

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

Merk ook op dat we QUOTENAME hebben gebruikt. Dit zorgt ervoor dat tabelnamen met spatie geen fout veroorzaken door ze tussen vierkante haken te plaatsen.

Maar hoe zit het met het valideren van de sleutelkolom? U kunt het bestaan ​​van de kolom van de doeltabel controleren in 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

Hier is het volledige script van wat we willen bereiken.

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

Het resultaat van dit script staat in Afbeelding 7 hieronder.

U kunt dit proberen met andere tabellen. Verander eenvoudig de @object , @idkey , en @id variabele waarden.

Geen voorziening voor foutopsporing

Er kunnen fouten optreden. U moet dus de gegenereerde dynamische SQL-tekenreeks kennen om de hoofdoorzaak te vinden. We zijn geen waarzeggers of goochelaars om de vorm van de dynamische SQL-string te raden. Je hebt dus een foutopsporingsvlag nodig.

Merk in Afbeelding 7 eerder op dat de dynamische SQL-string wordt afgedrukt op het tabblad Berichten van SSMS. We hebben een @isDebug . toegevoegd BIT-variabele en stel deze in op 1 in code. Als de waarde 1 is, wordt de dynamische SQL-string afgedrukt. Dit is handig als u een script of een opgeslagen procedure als deze moet debuggen. Zet het gewoon terug op nul als u klaar bent met debuggen. Als dit een opgeslagen procedure is, maakt u van deze vlag een optionele parameter met een standaardwaarde van nul.

Om de dynamische SQL-string te zien, kunt u 2 mogelijke methoden gebruiken.

  • Gebruik PRINT als de tekenreeks kleiner is dan of gelijk is aan 8000 tekens.
  • Of gebruik SELECT als de string langer is dan 8000 tekens.

Slecht presterende dynamische SQL gebruikt in SP_EXECUTESQL

SP_EXECUTESQL kan traag zijn als u er een langzaam lopende query aan toewijst. Punt uit. Dit geeft nog geen problemen met het snuiven van parameters.

Begin dus statisch met de code die u dynamisch wilt uitvoeren. Controleer vervolgens het Uitvoeringsplan en STATISTIEKEN IO. Kijk of er indexen ontbreken die u moet maken. Stem het vroeg af.

De kern van SP_EXECUTESQL

Het gebruik van SP_EXECUTESQL is als het hanteren van een krachtig wapen. Maar degene die het hanteert, moet er bedreven in zijn. Al is dit ook geen rocket science. Als je vandaag een nieuweling bent, kan het in de loop van de tijd gezond verstand worden met oefenen.

Deze lijst met valkuilen is niet compleet. Maar het omvat de gewone. Als je meer informatie nodig hebt, bekijk dan deze links:

  • De vloek en zegeningen van dynamische SQL, door Erland Sommarskog
  • SP_EXECUTESQL (Transact-SQL), door Microsoft

Zoals dit? Deel het dan op je favoriete social media platforms. U kunt uw beproefde tips ook met ons delen in het gedeelte Opmerkingen.


  1. Voer MySQLDump uit zonder tabellen te vergrendelen

  2. Ongeldige Oracle-URL opgegeven:OracleDataSource.makeURL

  3. Een processor selecteren voor SQL Server 2014 – deel 1

  4. Go with SQL Server-stuurprogramma kan geen verbinding maken, inloggen mislukt