sql >> Databasteknik >  >> RDS >> Sqlserver

10 SP_EXECUTESQL Gotchas att undvika för bättre dynamisk SQL

Vet du hur kraftfullt ett verktyg som dynamisk SQL kan vara? Använd det på fel sätt och du kan tillåta någon att ta över din databas. Dessutom kan det vara för mycket komplexitet. Den här artikeln syftar till att introducera fallgroparna när du använder SP_EXECUTESQL och erbjuder 10 vanligaste gotchas att undvika.

SP_EXECUTESQL är ett av sätten du kan köra SQL-kommandon inbäddade i en sträng. Du bygger denna sträng dynamiskt genom koden. Det är därför vi kallar detta dynamisk SQL. Bortsett från en serie påståenden, kan du också skicka in en lista med parametrar och värden. Faktum är att dessa parametrar och värden skiljer sig från EXEC-kommandot. EXEC accepterar inte parametrar för dynamisk SQL. Ändå kör du SP_EXECUTESQL med EXEC!

För en nybörjare till dynamisk SQL, så här anropar du detta.

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

Du bildar strängen av kommandon som inkluderar giltiga SQL-satser. Alternativt kan du skicka en lista med ingångs- eller utdataparametrar och deras datatyper. Och slutligen passerar du en kommaseparerad lista med värden. Om du skickar parametrar måste du skicka värden. Senare kommer du att se både rätt och fel exempel på detta när du läser vidare.

Använda SP_EXECUTESQL när du inte behöver det

Det är rätt. Om du inte behöver den, använd den inte. Om detta blir de 10 budorden för SP_EXECUTESQL, är detta det första. Det beror på att denna systemprocedur lätt kan missbrukas. Men hur vet du det?

Svara så här:Finns det ett problem om kommandot i din dynamiska SQL blir statiskt? Om du inte har något att säga på den här punkten, behöver du det inte. Se exemplet.

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

Som du kan se är användningen av SP_EXECUTESQL komplett med en kommandosträng, parameter och värde. Men behöver det vara så här? Säkerligen inte. Det är helt ok att ha detta:

DECLARE @productID INT = 1;

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

Men jag kanske blir generad när du ser exemplen längre fram i artikeln. För jag kommer att motsäga det jag påstår i denna allra första punkt. Du kommer att se korta dynamiska SQL-satser som är bättre än statiska. Så håll ut med mig eftersom exemplen bara kommer att bevisa de punkter som beskrivs här. För resten av exemplen, låtsas ett tag att du tittar på koden avsedd för dynamisk SQL.

Objekt och variabler utanför omfånget

Att köra en serie SQL-kommandon i en sträng med SP_EXECUTESQL är som att skapa en namnlös lagrad procedur och köra den. Genom att veta detta kommer objekt som temporära tabeller och variabler utanför kommandosträngen att vara utanför omfånget. På grund av detta kommer körtidsfel att uppstå.

När du använder SQL-variabler

Kolla in det här.

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

Variabeln @extraText är osynlig för exekverade kommandon. Istället för detta är en bokstavlig sträng eller variabeln som deklareras inuti den dynamiska SQL-strängen mycket bättre. Hur som helst, resultatet är:

Såg du det felet i bild 1? Om du behöver skicka ett värde inuti den dynamiska SQL-strängen, lägg till en annan parameter.

När du använder tillfälliga tabeller

Tillfälliga tabeller i SQL Server finns också inom ramen för en modul. Så, vad tycker du om den här koden?

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

Koden ovan exekverar 2 lagrade procedurer i följd. Den temporära tabellen som skapats från den första dynamiska SQL-koden kommer inte att vara tillgänglig för den andra. Som ett resultat kommer du att få ett Ogiltigt objektnamn #TempNames fel.

SQL Server QUOTENAME Misstag

Här är ett annat exempel som kommer att se bra ut vid första anblicken, men som kommer att orsaka ett Invalid Object Name-fel. Se koden nedan.

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

För att vara giltig för SELECT, bifoga schemat och tabellen med hakparenteser så här:[Schema].[Tabell] . Eller omslut dem inte alls (såvida inte tabellnamnet innehåller ett eller flera mellanslag). I exemplet ovan användes inga hakparenteser för både tabellen och schemat. Istället för att använda @Table som en parameter blev den en platshållare för REPLACE. QUOTENAME användes.

QUOTENAME omsluter en sträng med en avgränsare. Detta är också bra för att hantera enstaka citattecken i den dynamiska SQL-strängen. Den hakparentes är standardavgränsaren. Så, i exemplet ovan, vad tror du att QUOTENAME gjorde? Kontrollera figur 2 nedan.

SQL PRINT-satsen hjälpte oss att felsöka problemet genom att skriva ut den dynamiska SQL-strängen. Nu vet vi problemet. Vad är det bästa sättet att fixa detta? En lösning är koden nedan:

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

Låt oss förklara detta.

Först, @Table används som platshållare för REPLACE. Den kommer att söka efter förekomsten av @Table och ersätt den med rätt värden. Varför? Om detta används som en parameter kommer ett fel att uppstå. Det är också anledningen till att använda REPLACE.

Sedan använde vi PARSENAME. Strängen vi skickade till den här funktionen kommer att separera den till schema och tabell. PARSENAME(@tableName,2) kommer att få schemat. Medan PARSENAME(@tableName,1) kommer att få bordet.

Slutligen kommer QUOTENAME att omsluta schemat och tabellnamnen separat efter att PARSENAME är klar. Resultatet är [Person].[Person] . Nu är det giltigt.

Ett bättre sätt att rensa den dynamiska SQL-strängen med objektnamn kommer dock att visas senare.

Kör SP_EXECUTESQL med en NULL-sats

Det finns dagar då du är upprörd eller nedstämd. Det kan göras misstag längs vägen. Lägg sedan till en lång dynamisk SQL-sträng till mixen och NULL. Och resultatet?

Ingenting.

SP_EXECUTESQL ger dig ett tomt resultat. På vilket sätt? Genom att sammanfoga en NULL av misstag. Tänk på exemplet nedan:

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

Till en början ser koden bra ut. Ändå kommer örnögonen bland oss ​​att lägga märke till @crlf variabel. dess värde? Variabeln initierades inte. Så det är NULL.

Men vad är poängen med den variabeln? I ett senare avsnitt kommer du att veta hur viktigt det är för formatering och felsökning. Låt oss nu fokusera på det aktuella.

Först, sammanlänkning av en NULL-variabel till den dynamiska SQL-strängen kommer att resultera till NULL. Sedan kommer PRINT att skriva ut tomt. Slutligen kommer SP_EXECUTESQL att fungera bra med den dynamiska SQL-strängen NULL. Men det ger ingenting.

NULLs kan fascinera oss på en redan dålig dag. Ta en kort paus. koppla av. Kom sedan tillbaka med ett klarare sinne.

Infoga parametervärden

Stökigt.

Det är så inlining av värden till dynamisk SQL-sträng kommer att se ut. Det kommer att finnas massor av enstaka citat för stråkar och datum. Om du inte är försiktig kommer O'Briens och O'Neils också att orsaka fel. Och eftersom dynamisk SQL är en sträng måste du KONVERTERA eller CASTA värdena till en sträng. Här är ett exempel.

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;

Jag såg stökigare dynamiska strängar än så här. Lägg märke till de enskilda citattecken, CONVERT och CAST. Om parametrar användes skulle detta kunna se bättre ut. Du kan se det nedan

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

Ser? Mindre enstaka citat, ingen CONVERT och CAST, och renare också.

Men det finns en ännu farligare bieffekt av inline-värden.

SQL-injektion

Om vi ​​levde i en värld där alla människor var bra, skulle SQL-injektion aldrig vara tänkt på. Men så är inte fallet. Någon kan injicera skadlig SQL-kod i din. Hur kan detta hända?

Här är scenariot vi ska använda i vårt exempel:

  • Värden är sammansmälta med den dynamiska SQL-strängen som i vårt exempel tidigare. Inga parametrar.
  • Den dynamiska SQL-strängen är upp till 2 GB med NVARCHAR(MAX). Mycket utrymme för att injicera skadlig kod.
  • Det värsta är att den dynamiska SQL-strängen exekveras med förhöjda behörigheter.
  • SQL Server-instansen accepterar SQL-autentisering.

Är detta för mycket? För system som hanteras av en man kan detta hända. Ingen kollar honom ändå. För större företag finns det ibland en slapp IT-avdelning med säkerhet.

Är detta fortfarande ett hot idag? Det är, enligt molntjänstleverantören Akamai i deras rapport om Internet/säkerhet. Mellan november 2017 och mars 2019 representerar SQL-injektion nästan två tredjedelar av alla webbapplikationsattacker. Det är det högsta av alla undersökta hot. Mycket dåligt.

Vill du se det själv?

SQL Injection Practice: Dåligt exempel

Låt oss göra lite SQL-injektion i det här exemplet. Du kan prova detta i ditt eget AdventureWorks databas. Men se till att SQL-autentisering är tillåten och att du kör den med förhöjda behörigheter.

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

Koden ovan representerar inte faktisk kod från ett befintligt företag. Det går inte ens att anropa av en app. Men detta illustrerar den onda gärningen. Så, vad har vi här?

Först kommer den injicerade koden att skapa ett SQL-konto som ser ut som sa , men det är inte. Och som sa , detta har sysadmin behörigheter. Så småningom kommer detta att användas för att komma åt databasen när som helst med fullständiga rättigheter. Allt är möjligt när detta är gjort:stjäla, radera, manipulera företagsdata, you name it.

Kommer den här koden att köras? Definitivt! Och när det väl är det kommer superkontot att skapas tyst. Och, naturligtvis, kommer adressen till Zheng Mu att visas i resultatuppsättningen. Allt annat är normalt. Shady, tycker du inte?

När du har kört ovanstående kod, försök att köra den här också:

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

Om det ger 1 är han med, vem som än är är. Alternativt kan du kontrollera det i din SQL Servers säkerhetsinloggningar i SQL Server Management Studio.

Så, vad fick du?

Skrämmande, är det inte? (Om detta är verkligt så är det det.)

SQL Injection Practice:Bra exempel

Låt oss nu ändra koden lite genom att använda parametrar. Övriga villkor är fortfarande desamma.

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

Det finns 2 skillnader i resultatet jämfört med det första exemplet.

  • För det första visas inte Zheng Mus adress. Resultatuppsättningen är tom.
  • Då skapas inte det övergivna kontot. Om du använder IS_SRVROLEMEMBER returneras NULL.

Vad hände?

Eftersom parametrar används, värdet på @firstName är 'Zheng"; SKAPA LOGIN MED LÖSENORD=”12345”; ALT’ . Detta tas som ett bokstavligt värde och trunkeras till endast 50 tecken. Kontrollera förnamnsparametern i koden ovan. Det är NVARCHAR(50). Det är därför resultatuppsättningen är tom. Ingen person med ett sådant förnamn finns i databasen.

Detta är bara ett exempel på SQL-injektion och ett sätt att undvika det. Det är mer involverat i att göra verkliga saker. Men jag hoppas att jag klargjorde varför inline-värden i dynamisk SQL är dåliga.

Parametersniffning

Har du upplevt en långsam körning lagrad procedur från en app, men när du försökte köra den i SSMS blev den snabb? Det är förbryllande eftersom du använde de exakta parametervärdena som används i appen.

Det är parametersnuffning i aktion. SQL Server skapar en exekveringsplan första gången den lagrade proceduren körs eller kompileras om. Återanvänd sedan planen för nästa körning. Det låter bra eftersom SQL Server inte behöver återskapa planen varje gång. Men det finns tillfällen då ett annat parametervärde behöver en annan plan för att köras snabbt.

Här är en demonstration med SP_EXECUTESQL och en vanlig statisk 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

Den här är väldigt enkel. Kontrollera utförandeplanen i figur 3.

Låt oss nu prova samma fråga med statisk SQL.

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

Kolla in figur 4 och jämför den sedan med figur 3.

I figur 3, Indexsökning och Nested Loop används. Men i figur 4 är det en Clustered Index Scan . Även om det inte finns något märkbart prestationsstraff vid denna tidpunkt, visar detta att parametersnuffning inte bara är en fantasi.

Detta kan vara mycket frustrerande när frågan blir långsam. Du kan sluta använda tekniker som att kompilera om eller använda frågetips för att undvika det. Alla dessa har nackdelar.

Oformaterad dynamisk SQL-sträng i SP_EXECUTESQL

Vad kan gå fel med den här koden?

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

Det är kort och enkelt. Men kolla figur 5 nedan.

Fel uppstår om du inte har något emot ett enda mellanslag mellan nyckelord och objekt när du bildar den dynamiska SQL-strängen. Som i figur 5, där ProductCount kolumnalias och nyckelordet FROM har inget mellanslag mellan. Det blir förvirrande när en del av en sträng flyter ner till nästa kodrad. Det får dig att tro att syntaxen är korrekt.

Observera också att strängen använde 2 rader i kodfönstret, men utdata från PRINT visar 1 rad. Tänk om det här är en väldigt, väldigt lång kommandosträng. Det är svårt att hitta problemet förrän du formaterar strängen korrekt från fliken Meddelanden.

För att lösa detta problem, lägg till en vagnretur och radmatning. Du märker förmodligen en variabel @crlf från de tidigare exemplen. Att formatera din dynamiska SQL-sträng med blanksteg och en ny rad kommer att göra den dynamiska SQL-strängen mer läsbar. Det här är också bra för felsökning.

Överväg en SELECT-sats med JOIN. Den behöver flera rader kod som exemplet nedan.

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

För att formatera strängen, @crlf variabeln är satt till NCHAR(13), en vagnretur, och NCHAR(10), en radmatning. Den sammanfogas till varje rad för att bryta en lång sträng av SELECT-satsen. För att se resultatet på fliken Meddelanden använder vi PRINT. Kontrollera utgången i figur 6 nedan.

Hur du formar den dynamiska SQL-strängen är upp till dig. Vad som än passar dig för att göra det tydligt, läsbart och lätt att felsöka när det är dags.

Osanitiserade objektnamn

Behöver du dynamiskt ställa in tabell-, vy- eller databasnamn av någon anledning? Sedan måste du "sanera" eller validera dessa objektnamn för att undvika fel.

I vårt exempel kommer vi att använda ett tabellnamn, även om valideringsprincipen kan gälla även för vyer. Hur du hanterar det härnäst kommer att vara annorlunda.

Tidigare använde vi PARSENAME för att skilja schemanamnet från tabellnamnet. Den kan också användas om strängen har ett server- och databasnamn. Men i det här exemplet kommer vi bara att använda schema- och tabellnamn. Jag lämnar resten till era briljanta sinnen. Detta kommer att fungera oavsett om du namnger dina tabeller med eller utan mellanslag. Mellanslag på tabell- eller vynamn är giltiga. Så det fungerar för dbo.MyFoodCravings eller [dbo].[My Food Cravings] .

EXEMPEL

Låt oss skapa en tabell.

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

Sedan skapar vi ett skript som kommer att använda SP_EXECUTESQL. Detta kommer att köra en generisk DELETE-sats för alla tabeller med en 1-kolumnsnyckel. Det första du ska göra är att analysera det fullständiga objektnamnet.

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

På så sätt skiljer vi schemat från tabellen. För att validera ytterligare använder vi OBJECT_ID. Om det inte är NULL är det giltigt.

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

Observera också att vi använde QUOTENAME. Detta säkerställer att tabellnamn med mellanslag inte utlöser ett fel genom att omsluta dem med hakparenteser.

Men vad sägs om att validera nyckelkolumnen? Du kan kontrollera existensen av kolumnen i måltabellen i 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

Nu, här är hela manuset till vad vi vill åstadkomma.

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

Resultatet av detta skript finns i figur 7 nedan.

Du kan prova detta med andra bord. Ändra helt enkelt @object , @idkey och @id variabelvärden.

Inga bestämmelser för felsökning

Fel kan hända. Så du måste känna till den genererade dynamiska SQL-strängen för att hitta rotorsaken. Vi är inte spådamer eller magiker för att gissa formen på den dynamiska SQL-strängen. Så du behöver en felsökningsflagga.

Lägg märke till i figur 7 tidigare att den dynamiska SQL-strängen skrivs ut på fliken Meddelanden i SSMS. Vi har lagt till en @isDebug BIT-variabel och ställ in den till 1 i kod. När värdet är 1 skrivs den dynamiska SQL-strängen ut. Detta är bra om du behöver felsöka ett skript eller lagrad procedur som denna. Ställ bara tillbaka den till noll när du är klar med felsökningen. Om detta är en lagrad procedur, gör denna flagga till en valfri parameter med standardvärdet noll.

För att se den dynamiska SQL-strängen kan du använda två möjliga metoder.

  • Använd PRINT om strängen är mindre än eller lika med 8000 tecken.
  • Eller använd SELECT om strängen är mer än 8000 tecken.

Dåligt presterande dynamisk SQL används i SP_EXECUTESQL

SP_EXECUTESQL kan vara långsam om du tilldelar den en långsam sökning. Period. Detta innebär inga problem med parametersniffning ännu.

Så, börja statiskt med koden du vill köra dynamiskt. Kontrollera sedan exekveringsplanen och STATISTIK IO. Se om det saknas index som du behöver skapa. Ställ in det tidigt.

The Bottomline i SP_EXECUTESQL

Att använda SP_EXECUTESQL är som att använda ett kraftfullt vapen. Men den som använder den måste vara skicklig på det. Även om detta inte heller är raketvetenskap. Om du är nybörjare idag kan det bli sunt förnuft i takt med träningen.

Den här listan över gotchas är inte komplett. Men det täcker de vanliga. Om du behöver mer information, kolla in dessa länkar:

  • The Curse and Blessings of Dynamic SQL, av Erland Sommarskog
  • SP_EXECUTESQL (Transact-SQL), av Microsoft

Så här? Dela den sedan på dina favoritplattformar för sociala medier. Du kan också dela dina beprövade tips med oss ​​i kommentarsektionen.


  1. Hur man använder PL/SQL Bulk Collect-klausul med FETCH INTO-utlåtande

  2. ORA-01461:kan binda ett LONG-värde endast för att infogas i en LONG-kolumn - Förekommer vid fråga

  3. Använd TYPE_NAME() för att hämta namnet på en datatyp i SQL Server

  4. AVG() – Beräkna medelvärdet för en kolumn i MySQL