sql >> Database teknologi >  >> RDS >> Sqlserver

10 SP_EXECUTESQL Gotchas at undgå for bedre dynamisk SQL

Ved du, hvor stærkt et værktøj som dynamisk SQL kan være? Brug det på den forkerte måde, og du kan tillade nogen at overtage din database. Derudover kan der være for meget kompleksitet. Denne artikel har til formål at introducere faldgruberne ved brug af SP_EXECUTESQL og tilbyder 10 mest almindelige gotchas, der skal undgås.

SP_EXECUTESQL er en af ​​måderne, du kan køre SQL-kommandoer indlejret i en streng. Du bygger denne streng dynamisk gennem koden. Det er derfor, vi kalder dette dynamisk SQL. Udover en række udsagn kan du også sende en liste over parametre og værdier ind i den. Faktisk adskiller disse parametre og værdier sig fra EXEC-kommandoen. EXEC accepterer ikke parametre for dynamisk SQL. Alligevel udfører du SP_EXECUTESQL ved hjælp af EXEC!

For en nybegynder til dynamisk SQL, her er, hvordan du påberåber dette.

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

Du danner strengen af ​​kommandoer, der inkluderer gyldige SQL-sætninger. Du kan eventuelt sende en liste over input- eller outputparametre og deres datatyper. Og endelig sender du en kommasepareret liste over værdier. Hvis du sender parametre, skal du videregive værdier. Senere vil du se både rigtige og forkerte eksempler på dette, mens du læser videre.

Brug af SP_EXECUTESQL, når du ikke har brug for det

Det er rigtigt. Hvis du ikke har brug for det, skal du ikke bruge det. Hvis dette bliver de 10 bud i SP_EXECUTESQL, er dette den første. Det er fordi denne systemprocedure let kan misbruges. Men hvordan ved du det?

Svar på dette:Er der et problem, hvis kommandoen i din dynamiske SQL bliver statisk? Hvis du ikke har noget at sige på dette punkt, så har du ikke brug for det. Se eksemplet.

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, er brugen af ​​SP_EXECUTESQL komplet med en kommandostreng, parameter og værdi. Men behøver det at være sådan? Sikkert ikke. Det er helt fint at have dette:

DECLARE @productID INT = 1;

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

Men jeg bliver måske flov, når du ser eksemplerne længere nede i artiklen. For jeg vil modsige det, jeg hævder i dette allerførste punkt. Du vil se korte dynamiske SQL-sætninger, der er bedre end statiske. Så bær over med mig, fordi eksemplerne blot vil bevise de punkter, der er skitseret her. For resten af ​​eksemplerne, lad som om du i et stykke tid ser på koden beregnet til dynamisk SQL.

Objekter og variabler uden for rammerne

At køre en række SQL-kommandoer i en streng ved hjælp af SP_EXECUTESQL er som at oprette en navnløs lagret procedure og køre den. Ved at vide dette vil objekter som midlertidige tabeller og variabler uden for kommandostrengen være uden for rækkevidde. På grund af dette vil der opstå kørselsfejl.

Når du bruger SQL-variabler

Tjek dette ud.

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

Variablen @extraText er usynlig for udførte kommandoer. I stedet for dette er en bogstavelig streng eller variabel erklæret inde i den dynamiske SQL-streng meget bedre. Under alle omstændigheder er resultatet:

Så du den fejl i figur 1? Hvis du har brug for at sende en værdi inde i den dynamiske SQL-streng, skal du tilføje en anden parameter.

Ved brug af midlertidige tabeller

Midlertidige tabeller i SQL Server findes også inden for et moduls omfang. Så hvad synes du om denne kode?

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

Ovenstående kode udfører 2 lagrede procedurer efter hinanden. Den midlertidige tabel oprettet fra den første dynamiske SQL vil ikke være tilgængelig for den anden. Som et resultat vil du få et Ugyldigt objektnavn #TempNames fejl.

SQL Server QUOTENAME fejl

Her er et andet eksempel, der vil se fint ud ved første øjekast, men som vil forårsage en fejl med ugyldigt objektnavn. Se koden nedenfor.

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

For at være gyldig for SELECT skal du omslutte skemaet og tabellen med firkantede parenteser som denne:[Skema].[Tabel] . Eller omslut dem slet ikke (medmindre tabelnavnet indeholder et eller flere mellemrum). I eksemplet ovenfor blev der ikke brugt firkantede parenteser til både tabellen og skemaet. I stedet for at bruge @Table som en parameter blev det en pladsholder for REPLACE. QUOTENAME blev brugt.

QUOTENAME omslutter en streng med et skilletegn. Dette er også godt til at håndtere enkelte anførselstegn i den dynamiske SQL-streng. Den firkantede parentes er standardafgrænseren. Så i eksemplet ovenfor, hvad tror du, QUOTENAME gjorde? Tjek figur 2 nedenfor.

SQL PRINT-sætningen hjalp os med at fejlfinde problemet ved at udskrive den dynamiske SQL-streng. Nu kender vi problemet. Hvad er den bedste måde at løse dette på? En løsning er koden nedenfor:

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

Lad os forklare dette.

Først @Table bruges som pladsholder for REPLACE. Den vil søge efter forekomsten af ​​@Table og erstatte det med de korrekte værdier. Hvorfor? Hvis dette bruges som parameter, vil der opstå en fejl. Det er også grunden til at bruge REPLACE.

Derefter brugte vi PARSENAME. Den streng, vi sendte til denne funktion, vil adskille den til skema og tabel. PARSENAVN(@tabelnavn,2) får skemaet. Mens PARSENAME(@tableName,1) får bordet.

Endelig vil QUOTENAME omslutte skemaet og tabelnavnene separat, efter at PARSENAME er færdig. Resultatet er [Person].[Person] . Nu er det gyldigt.

En bedre måde at rense den dynamiske SQL-streng med objektnavne på vil dog blive vist senere.

Udførelse af SP_EXECUTESQL med en NULL-sætning

Der er dage, hvor du er ked af det eller nedslået. Der kan laves fejl undervejs. Tilføj derefter en lang dynamisk SQL-streng til blandingen og NULL'erne. Og resultatet?

Intet.

SP_EXECUTESQL vil give dig et tomt resultat. Hvordan? Ved at sammenkæde en NULL ved en fejltagelse. Overvej eksemplet nedenfor:

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

Først ser koden godt ud. Alligevel vil ørneøjnene blandt os bemærke @crlf variabel. dens værdi? Variablen blev ikke initialiseret. Så den er NULL.

Men hvad er meningen med den variabel? I et senere afsnit vil du vide, hvor vigtigt det er ved formatering og fejlretning. Lad os nu fokusere på det aktuelle punkt.

Først vil sammenkædning af en NULL-variabel til den dynamiske SQL-streng resultere til NULL. Derefter udskriver PRINT tomt. Endelig vil SP_EXECUTESQL køre fint med den dynamiske SQL-streng NULL. Men det giver intet tilbage.

NULLs kan fascinere os på en allerede dårlig dag. Hold en kort pause. Slap af. Så kom tilbage med et klarere sind.

Inlining parameterværdier

Rodet.

Sådan vil inlining-værdier til dynamisk SQL-streng se ud. Der vil være masser af enkelte citater til strenge og datoer. Hvis du ikke er forsigtig, vil O'Briens og O'Neils også forårsage fejl. Og da dynamisk SQL er en streng, skal du KONVERTERE eller CAST værdierne til streng. Her er et eksempel.

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;

Jeg så mere rodede dynamiske strenge end dette. Læg mærke til de enkelte anførselstegn, KONVERTER og CAST. Hvis parametre blev brugt, kunne dette se bedre ud. Du kan se det nedenfor

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

Se? Færre enkelte citater, ingen CONVERT og CAST, og også renere.

Men der er en endnu farligere bivirkning af inline-værdier.

SQL-injektion

Hvis vi levede i en verden, hvor alle mennesker var gode, ville der aldrig være tænkt på SQL-injektion. Men det er ikke tilfældet. Nogen kan injicere ondsindet SQL-kode i din. Hvordan kan dette ske?

Her er det scenarie, vi skal bruge i vores eksempel:

  • Værdier fusioneres til den dynamiske SQL-streng som vores tidligere eksempel. Ingen parametre.
  • Den dynamiske SQL-streng er op til 2 GB ved brug af NVARCHAR(MAX). Meget plads til at injicere ondsindet kode.
  • Værst er det, at den dynamiske SQL-streng udføres med forhøjede tilladelser.
  • SQL Server-instansen accepterer SQL-godkendelse.

Er det for meget? For systemer, der styres af én mand, kan dette ske. Ingen tjekker ham alligevel. For større virksomheder findes der nogle gange en it-afdeling, der er slap med sikkerhed.

Er dette stadig en trussel i dag? Det er ifølge cloud-tjenesteudbyder Akamai i deres State of the Internet/Security-rapport. Mellem november 2017 og marts 2019 repræsenterer SQL-injektion næsten to tredjedele af alle webapplikationsangreb. Det er den højeste af alle de undersøgte trusler. Meget dårligt.

Vil du selv se det?

SQL-injektionspraksis: Dårligt eksempel

Lad os lave noget SQL-injektion i dette eksempel. Du kan prøve dette i dit eget AdventureWorks database. Men sørg for, at SQL-godkendelse er tilladt, og at du kører det med forhøjede tilladelser.

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 ovenfor repræsenterer ikke faktisk kode fra en eksisterende virksomhed. Det kan ikke engang kaldes af en app. Men dette illustrerer den onde gerning. Så hvad har vi her?

Først vil den injicerede kode oprette en SQL-konto, der ligner sa , men det er det ikke. Og gerne sa , dette har sysadmin tilladelser. Til sidst vil dette blive brugt til at få adgang til databasen når som helst med fulde privilegier. Alt er muligt, når dette er gjort:stjæle, slette, manipulere virksomhedsdata, you name it.

Vil denne kode køre? Helt bestemt! Og når den først er det, oprettes superkontoen lydløst. Og selvfølgelig vil adressen på Zheng Mu vises i resultatsættet. Alt andet er normalt. Shady, synes du ikke?

Når du har kørt ovenstående kode, så prøv også at køre denne:

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

Hvis det returnerer 1, er han med, uanset hvem dette er er. Alternativt kan du tjekke det i din SQL Servers sikkerhedslogins i SQL Server Management Studio.

Så hvad fik du?

Skræmmende, er det ikke? (Hvis dette er rigtigt, er det det.)

SQL-injektionspraksis:godt eksempel

Lad os nu ændre koden lidt ved at bruge parametre. De øvrige betingelser er stadig de samme.

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

Der er 2 forskelle i resultatet i forhold til det første eksempel.

  • For det første vises Zheng Mus adresse ikke. Resultatsættet er tomt.
  • Derefter oprettes den frafaldne konto ikke. Brug af IS_SRVROLEMEMBER vil returnere NULL.

Hvad skete der?

Da der bruges parametre, er værdien af ​​@fornavn er 'Zheng"; OPRET LOGIN sà MED PASSWORD=”12345”; ALT’ . Dette tages som en bogstavelig værdi og kun afkortet til 50 tegn. Tjek fornavnsparameteren i koden ovenfor. Det er NVARCHAR(50). Derfor er resultatsættet tomt. Ingen person med et sådant fornavn er i databasen.

Dette er blot et eksempel på SQL-injektion og en måde at undgå det på. Der er mere involveret i at gøre virkelige ting. Men jeg håber, jeg gjorde pointen klart, hvorfor inline-værdier i dynamisk SQL er dårlige.

Parametersniffing

Har du oplevet en langsomt kørende lagret procedure fra en app, men da du prøvede at køre den i SSMS, blev den hurtig? Det er forvirrende, fordi du brugte de nøjagtige parameterværdier, der blev brugt i appen.

Det er parametersniffing i aktion. SQL Server opretter en eksekveringsplan første gang, den lagrede procedure køres eller genkompileres. Genbrug derefter planen til næste løb. Det lyder godt, fordi SQL Server ikke behøver at genskabe planen hver gang. Men der er tidspunkter, hvor en anden parameterværdi kræver en anden plan for at køre hurtigt.

Her er en demonstration ved hjælp af SP_EXECUTESQL og en almindelig 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

Denne er meget enkel. Tjek udførelsesplanen i figur 3.

Lad os nu prøve den samme forespørgsel ved hjælp af statisk SQL.

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

Se figur 4, og sammenlign den derefter med figur 3.

I figur 3, Indekssøgning og Indlejret løkke er brugt. Men i figur 4 er det en Clustered Index Scan . Selvom der ikke er nogen mærkbar præstationsstraf på dette tidspunkt, viser dette, at parametersnus ikke kun er en fantasi.

Dette kan være meget frustrerende, når forespørgslen bliver langsom. Du kan ende med at bruge teknikker som at genkompilere eller bruge forespørgselstip for at undgå det. Enhver af disse har ulemper.

Uformateret dynamisk SQL-streng i SP_EXECUTESQL

Hvad kan gå galt med denne kode?

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

Det er kort og enkelt. Men tjek figur 5 nedenfor.

Der opstår fejl, hvis du ikke har noget imod et enkelt mellemrum mellem nøgleord og objekter, når du danner den dynamiske SQL-streng. Som i figur 5, hvor ProductCount kolonnealias og nøgleordet FROM har ingen mellemrum imellem. Det bliver forvirrende, når en del af en streng flyder ned til den næste kodelinje. Det får dig til at tro, at syntaksen er korrekt.

Bemærk også, at strengen brugte 2 linjer i kodevinduet, men outputtet fra PRINT viser 1 linje. Forestil dig, hvis dette er en meget, meget lang kommandostreng. Det er svært at finde problemet, før du formaterer strengen korrekt fra fanen Beskeder.

For at løse dette problem skal du tilføje en vognretur og linjeskift. Du bemærker sikkert en variabel @crlf fra de foregående eksempler. Formatering af din dynamiske SQL-streng med mellemrum og en ny linje vil gøre den dynamiske SQL-streng mere læsbar. Dette er også fantastisk til fejlfinding.

Overvej en SELECT-sætning med JOIN. Den har brug for flere linjer kode som eksemplet nedenfor.

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

For at formatere strengen skal du bruge @crlf variabel er indstillet til NCHAR(13), et vognretur, og NCHAR(10), et linjeskift. Den er sammenkædet til hver linje for at bryde en lang streng med SELECT-sætning. For at se resultatet på fanen Beskeder bruger vi PRINT. Tjek outputtet i figur 6 nedenfor.

Hvordan du danner den dynamiske SQL-streng er op til dig. Uanset hvad der passer dig for at gøre det klart, læsbart og nemt at fejlfinde, når tiden kommer.

Usanitiserede objektnavne

Har du brug for dynamisk at indstille tabel-, visnings- eller databasenavne af en eller anden grund? Derefter skal du "sanere" eller validere disse objektnavne for at undgå fejl.

I vores eksempel vil vi bruge et tabelnavn, selvom valideringsprincippet også kan gælde for visninger. Hvordan du håndterer det næste gang, vil være anderledes.

Tidligere brugte vi PARSENAME til at adskille skemanavnet fra tabelnavnet. Den kan også bruges, hvis strengen har et server- og databasenavn. Men i dette eksempel vil vi kun bruge skema- og tabelnavne. Jeg overlader resten til dine geniale sind. Dette fungerer, uanset om du navngiver dine tabeller med eller uden mellemrum. Mellemrum på tabel- eller visningsnavne er gyldige. Så det virker for dbo.MyFoodCravings eller [dbo].[Min madtrang] .

EKSEMPEL

Lad os oprette en tabel.

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

Derefter opretter vi et script, der vil bruge SP_EXECUTESQL. Dette vil køre en generisk DELETE-sætning for enhver tabel med en 1-kolonne nøgle. Den første ting at gøre er at parse det fulde objektnavn.

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

På denne måde adskiller vi skemaet fra tabellen. For at validere yderligere bruger vi OBJECT_ID. Hvis den ikke er NULL, så er den gyldig.

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

Bemærk også, at vi brugte QUOTENAME. Dette sikrer, at tabelnavne med mellemrum ikke udløser en fejl ved at omslutte dem med firkantede parenteser.

Men hvad med at validere nøglekolonnen? Du kan kontrollere eksistensen af ​​kolonnen 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

Her er det fulde manuskript til, hvad vi ønsker at opnå.

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 af dette script er i figur 7 nedenfor.

Du kan prøve dette med andre borde. Du skal blot ændre @object , @idkey og @id variable værdier.

Ingen mulighed for fejlretning

Der kan ske fejl. Så du skal kende den genererede dynamiske SQL-streng for at finde årsagen. Vi er ikke spåkoner eller tryllekunstnere til at gætte formen på den dynamiske SQL-streng. Så du har brug for et fejlfindingsflag.

Bemærk i figur 7 tidligere, at den dynamiske SQL-streng udskrives på fanen Meddelelser i SSMS. Vi tilføjede en @isDebug BIT variabel og sæt den til 1 i kode. Når værdien er 1, udskrives den dynamiske SQL-streng. Dette er godt, hvis du har brug for at fejlsøge et script eller en lagret procedure som denne. Bare sæt det tilbage til nul, når du er færdig med fejlretningen. Hvis dette er en lagret procedure, skal du gøre dette flag til en valgfri parameter med en standardværdi på nul.

For at se den dynamiske SQL-streng kan du bruge 2 mulige metoder.

  • Brug PRINT, hvis strengen er mindre end eller lig med 8000 tegn.
  • Eller brug SELECT, hvis strengen er på mere end 8000 tegn.

Dårligt ydende dynamisk SQL brugt i SP_EXECUTESQL

SP_EXECUTESQL kan være langsom, hvis du tildeler en langsomt kørende forespørgsel til den. Periode. Dette involverer endnu ikke problemer med parametersniffing.

Så start statisk med den kode, du vil køre dynamisk. Tjek derefter udførelsesplanen og STATISTIK IO. Se, om der mangler indekser, som du skal oprette. Juster det tidligt.

Bundlinjen i SP_EXECUTESQL

At bruge SP_EXECUTESQL er som at bruge et kraftfuldt våben. Men den, der bruger det, skal være dygtig til det. Selvom dette heller ikke er raketvidenskab. Hvis du er nybegynder i dag, kan det blive sund fornuft i takt med øvelsen.

Denne liste over gotchas er ikke komplet. Men det dækker over de almindelige. Hvis du har brug for mere information, så tjek disse links:

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

Sådan her? Så del det venligst på dine foretrukne sociale medieplatforme. Du kan også dele dine gennemtestede tips med os i kommentarsektionen.


  1. PostgreSQL-forespørgsel for at returnere resultater som en kommasepareret liste

  2. OPRET TABEL MySQL vs T-SQL med syntakseksempler

  3. hvordan man sletter dublerede rækker fra en tabel i mysql

  4. Dvaledialekt for Oracle Database 11g?