sql >> Databáze >  >> RDS >> Sqlserver

10 SP_EXECUTESQL Pro lepší dynamické SQL je třeba se vyhnout

Víte, jak mocný může být nástroj jako dynamické SQL? Použijte jej nesprávným způsobem a můžete někomu dovolit, aby převzal vaši databázi. Navíc to může být příliš složité. Tento článek si klade za cíl představit úskalí při používání SP_EXECUTESQL a nabízí 10 nejčastějších problémů, kterým je třeba se vyhnout.

SP_EXECUTESQL je jedním ze způsobů, jak můžete spouštět příkazy SQL vložené do řetězce. Tento řetězec vytvoříte dynamicky prostřednictvím kódu. Proto tomu říkáme dynamické SQL. Kromě řady příkazů do něj můžete předat také seznam parametrů a hodnot. Ve skutečnosti se tyto parametry a hodnoty liší od příkazu EXEC. EXEC nepřijímá parametry pro dynamické SQL. Přesto spouštíte SP_EXECUTESQL pomocí EXEC!

Pro nováčka v dynamickém SQL je zde návod, jak to vyvoláte.

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

Vytvoříte řetězec příkazů, které obsahují platné příkazy SQL. Volitelně můžete předat seznam vstupních nebo výstupních parametrů a jejich datových typů. A nakonec předáte seznam hodnot oddělených čárkami. Pokud předáváte parametry, musíte předat hodnoty. Později při dalším čtení uvidíte správné i nesprávné příklady.

Používání SP_EXECUTESQL, když to nepotřebujete

To je správně. Pokud to nepotřebujete, nepoužívejte to. Pokud se toto stane 10 příkazy SP_EXECUTESQL, toto je první. Tento systémový postup lze totiž snadno zneužít. Ale jak to víte?

Odpovězte na toto:Je problém, pokud se příkaz ve vašem dynamickém SQL stane statickým? Pokud k tomuto bodu nemáte co říci, pak to nepotřebujete. Viz příklad.

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

Jak můžete vidět, použití SP_EXECUTESQL je kompletní s příkazovým řetězcem, parametrem a hodnotou. Ale musí to tak být? Určitě ne. Je naprosto v pořádku mít toto:

DECLARE @productID INT = 1;

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

Ale mohl bych se stydět, když uvidíte příklady dále v článku. Protože budu v rozporu s tím, co tvrdím v tomto prvním bodě. Uvidíte krátké dynamické příkazy SQL, které jsou lepší než statické. Takže mějte strpení, protože příklady jen potvrzují body zde nastíněné. U zbývajících příkladů chvíli předstírejte, že se díváte na kód určený pro dynamické SQL.

Objekty a proměnné mimo rozsah

Spuštění řady příkazů SQL v řetězci pomocí SP_EXECUTESQL je jako vytvoření bezejmenné uložené procedury a její spuštění. S vědomím toho budou objekty jako dočasné tabulky a proměnné mimo příkazový řetězec mimo rozsah. Z tohoto důvodu dojde k chybám za běhu.

Při použití proměnných SQL

Podívejte se na toto.

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

Proměnná @extraText je neviditelný pro prováděné příkazy. Místo toho je mnohem lepší doslovný řetězec nebo proměnná deklarovaná v dynamickém řetězci SQL. Každopádně výsledek je:

Viděli jste chybu na obrázku 1? Pokud potřebujete předat hodnotu uvnitř dynamického řetězce SQL, přidejte další parametr.

Při použití dočasných tabulek

V rámci modulu existují také dočasné tabulky na serveru SQL Server. Co si tedy myslíte o tomto kódu?

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

Výše uvedený kód provádí 2 uložené procedury za sebou. Dočasná tabulka vytvořená z prvního dynamického SQL nebude přístupná druhému. V důsledku toho získáte Neplatný název objektu #TempNames chyba.

Chyba SQL Server QUOTENAME

Zde je další příklad, který bude na první pohled vypadat dobře, ale způsobí chybu Neplatný název objektu. Viz kód níže.

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

Aby bylo možné použít SELECT, uzavřete schéma a tabulku do hranatých závorek takto:[Schéma].[Tabulka] . Nebo je neuzavírejte vůbec (Pokud název tabulky neobsahuje jednu nebo více mezer). Ve výše uvedeném příkladu nebyly použity žádné hranaté závorky pro tabulku i schéma. Místo použití @Table jako parametr se stal zástupným symbolem pro REPLACE. Byl použit QUOTENAME.

QUOTENAME uzavírá řetězec s oddělovačem. To je také dobré pro práci s jednoduchými uvozovkami v dynamickém řetězci SQL. Hranatá závorka je výchozím oddělovačem. Co si tedy myslíte, že ve výše uvedeném příkladu udělal QUOTENAME? Zkontrolujte obrázek 2 níže.

Příkaz SQL PRINT nám pomohl odladit problém tiskem dynamického řetězce SQL. Nyní víme o problému. Jaký je nejlepší způsob, jak to opravit? Jedním z řešení je kód níže:

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

Pojďme si to vysvětlit.

Nejprve @Tabulka se používá jako zástupný symbol pro REPLACE. Bude hledat výskyt @Table a nahraďte jej správnými hodnotami. Proč? Pokud je toto použito jako parametr, dojde k chybě. To je také důvod pro použití REPLACE.

Poté jsme použili PARSENAME. Řetězec, který jsme předali této funkci, ji oddělí na schéma a tabulku. PARSENAME(@tableName,2) dostane schéma. Zatímco PARSENAME(@tableName,1) dostane stůl.

Nakonec QUOTENAME po dokončení PARSENAME uzavře názvy schémat a tabulek samostatně. Výsledkem je [Person].[Person] . Nyní je to platné.

Lepší způsob dezinfekce dynamického řetězce SQL pomocí názvů objektů však bude ukázán později.

Spuštění SP_EXECUTESQL s příkazem NULL

Jsou dny, kdy jste naštvaní nebo skleslí. Během cesty se dají udělat chyby. Poté do mixu a hodnot NULL přidejte dlouhý dynamický řetězec SQL. A výsledek?

Nic.

SP_EXECUTESQL vám poskytne prázdný výsledek. Jak? Omylem zřetězením NULL. Zvažte příklad níže:

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

Zpočátku kód vypadá dobře. Přesto si orlí oči mezi námi všimnou @crlf variabilní. Jeho hodnota? Proměnná nebyla inicializována. Takže je NULL.

Ale jaký je smysl této proměnné? V další části se dozvíte, jak důležité je formátování a ladění. Nyní se zaměřme na aktuální bod.

Za prvé, zřetězení proměnné NULL do dynamického řetězce SQL bude mít za následek hodnotu NULL. Poté bude příkaz PRINT tisknout prázdný. Nakonec SP_EXECUTESQL poběží dobře s dynamickým řetězcem SQL NULL. Ale nevrací nic.

Hodnoty NULL nás mohou hypnotizovat v už tak špatný den. Udělejte si krátkou přestávku. Odpočinout si. Pak se vraťte s jasnější myslí.

Vložení hodnot parametrů

Nepořádek.

Takto bude vypadat vkládání hodnot do dynamického řetězce SQL. Pro řetězce a data bude spousta jednoduchých uvozovek. Pokud nebudete opatrní, O'Briens a O'Neils také způsobí chyby. A protože dynamické SQL je řetězec, musíte hodnoty PŘEVÁDĚT nebo PŘEVÁDĚT na řetězec. Zde je příklad.

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;

Viděl jsem horší dynamické struny než tohle. Všimněte si jednoduchých uvozovek, CONVERT a CAST. Pokud by byly použity parametry, mohlo by to vypadat lépe. Můžete to vidět níže

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

Vidět? Méně jednoduchých uvozovek, žádné CONVERT a CAST a také čistší.

Ale je tu ještě nebezpečnější vedlejší účinek vložených hodnot.

Injekce SQL

Kdybychom žili ve světě, kde jsou všichni lidé dobří, nikdy by nás nenapadlo SQL injection. Ale není tomu tak. Někdo do toho vašeho může vložit škodlivý kód SQL. Jak se to může stát?

Zde je scénář, který použijeme v našem příkladu:

  • Hodnoty jsou sloučeny s dynamickým řetězcem SQL jako v našem předchozím příkladu. Žádné parametry.
  • Dynamický řetězec SQL má velikost až 2 GB pomocí NVARCHAR(MAX). Spousta prostoru pro vložení škodlivého kódu.
  • Nejhorší je, že dynamický řetězec SQL se spouští se zvýšenými oprávněními.
  • Instance serveru SQL Server přijímá ověřování SQL.

Je to příliš? U systémů spravovaných jedním člověkem se to může stát. Stejně ho nikdo nekontroluje. U větších společností někdy existuje oddělení IT laxní vůči zabezpečení.

Hrozí to i dnes? Je to podle poskytovatele cloudových služeb Akamai ve zprávě o stavu internetu/bezpečnosti. Od listopadu 2017 do března 2019 představuje SQL injection téměř dvě třetiny všech útoků na webové aplikace. To je nejvyšší ze všech zkoumaných hrozeb. Velmi špatné.

Chcete to vidět na vlastní oči?

Cvičení vkládání SQL:Špatný příklad

Udělejme v tomto příkladu nějakou injekci SQL. Můžete si to vyzkoušet ve svém vlastním AdventureWorks databáze. Ujistěte se však, že je povoleno ověřování SQL a že jej spouštíte se zvýšenými oprávněními.

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

Výše uvedený kód nepředstavuje skutečný kód z existující společnosti. Není to ani volatelné pomocí aplikace. Ale to ilustruje zlý skutek. Takže, co tu máme?

Nejprve vložený kód vytvoří účet SQL, který vypadá jako sa , ale není. A jako sa , má to sysadmin oprávnění. Nakonec to bude použito pro přístup k databázi kdykoli s plnými právy. Jakmile se to stane, je možné cokoli:krádež, mazání, manipulace s firemními daty, co si jen vzpomenete.

Poběží tento kód? Rozhodně! A jakmile to bude, superúčet bude vytvořen tiše. A v sadě výsledků se samozřejmě objeví adresa Zheng Mu. Všechno ostatní je normální. Shady, nemyslíš?

Po spuštění výše uvedeného kódu zkuste spustit také toto:

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

Pokud vrátí 1, je v, kdokoli to je. Případně to můžete zkontrolovat v přihlašovacích údajích zabezpečení serveru SQL Server v aplikaci SQL Server Management Studio.

Takže, co jste získali?

děsivé, že? (Pokud je to skutečné, tak je.)

Praxe SQL Injection:Dobrý příklad

Nyní trochu změníme kód pomocí parametrů. Ostatní podmínky jsou stále stejné.

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

Ve výsledku jsou 2 rozdíly oproti prvnímu příkladu.

  • Za prvé, adresa Zheng Mu se nezobrazí. Sada výsledků je prázdná.
  • Potom se účet odpadlíka nevytvoří. Použití IS_SRVROLEMEMBER vrátí hodnotu NULL.

Co se stalo?

Protože se používají parametry, hodnota @firstName je ‘Zheng“; VYTVOŘIT PŘIHLÁŠENÍ sà S HESLEM=”12345”; ALT’ . Tato hodnota se bere jako doslovná hodnota a je zkrácena pouze na 50 znaků. Zkontrolujte parametr křestního jména v kódu výše. Je to NVARCHAR(50). Proto je sada výsledků prázdná. Žádná osoba s takovýmto křestním jménem není v databázi.

Toto je jen jeden příklad SQL injection a jeden způsob, jak se tomu vyhnout. Je zde více zapojeno dělat skutečné věci. Ale doufám, že jsem vysvětlil, proč jsou vložené hodnoty v dynamickém SQL špatné.

Sniffování parametrů

Zažili jste pomalu běžící uloženou proceduru z aplikace, ale když jste ji zkusili spustit v SSMS, zrychlila se? Je to matoucí, protože jste použili přesné hodnoty parametrů použité v aplikaci.

To je čichání parametrů v akci. SQL Server vytvoří plán provádění při prvním spuštění nebo rekompilaci uložené procedury. Poté plán znovu použijte pro další spuštění. To zní skvěle, protože SQL Server nemusí plán pokaždé znovu vytvářet. Ale jsou chvíle, kdy jiná hodnota parametru potřebuje jiný plán, aby běžela rychle.

Zde je ukázka použití SP_EXECUTESQL a prostého statického 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

Tento je velmi jednoduchý. Zkontrolujte plán provádění na obrázku 3.

Nyní zkusme stejný dotaz pomocí statického SQL.

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

Podívejte se na obrázek 4 a porovnejte jej s obrázkem 3.

Na obrázku 3 Hledání indexu a Vnořená smyčka Jsou používány. Ale na obrázku 4 je to Clustered Index Scan . I když v tomto bodě neexistuje žádná rozpoznatelná penalizace výkonu, ukazuje to, že šňupání parametrů není jen představivost.

To může být velmi frustrující, jakmile se dotaz zpomalí. Můžete skončit používáním technik, jako je rekompilace nebo používání tipů pro dotazy, abyste se tomu vyhnuli. Každá z nich má své nevýhody.

Neformátovaný dynamický SQL řetězec v SP_EXECUTESQL

Co se s tímto kódem může pokazit?

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

Je to krátké a jednoduché. Ale podívejte se na obrázek 5 níže.

K chybám dochází, pokud vám při vytváření dynamického řetězce SQL nevadí jediná mezera mezi klíčovými slovy a objekty. Stejně jako na obrázku 5, kde je ProductCount alias sloupce a klíčové slovo FROM mezi nimi není mezera. Jakmile část řetězce přejde na další řádek kódu, začne to být matoucí. Nutí vás to myslet si, že syntaxe je správná.

Všimněte si také, že řetězec používal 2 řádky v okně kódu, ale výstup PRINT zobrazuje 1 řádek. Představte si, že se jedná o velmi, velmi dlouhý příkazový řetězec. Je těžké najít problém, dokud správně nenaformátujete řetězec na kartě Zprávy.

Chcete-li tento problém vyřešit, přidejte návrat vozíku a posun řádku. Pravděpodobně jste si všimli proměnné @crlf z předchozích příkladů. Formátováním dynamického řetězce SQL s mezerou a novým řádkem bude dynamický řetězec SQL čitelnější. To je také skvělé pro ladění.

Zvažte příkaz SELECT s JOIN. Vyžaduje několik řádků kódu, jako je příklad níže.

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

Pro formátování řetězce použijte @crlf proměnná je nastavena na NCHAR(13), návrat vozíku, a NCHAR(10), posun řádku. Je zřetězen ke každému řádku, aby se přerušil dlouhý řetězec příkazu SELECT. Chcete-li vidět výsledek na kartě Zprávy, použijeme TISK. Zkontrolujte výstup na obrázku 6 níže.

Jak vytvoříte dynamický řetězec SQL, je jen na vás. Cokoli, co vám vyhovuje, aby to bylo jasné, čitelné a snadno laditelné, až přijde čas.

Neupravené názvy objektů

Potřebujete z jakéhokoli důvodu dynamicky nastavit názvy tabulek, pohledů nebo databází? Poté musíte tyto názvy objektů „ošetřit“ nebo ověřit, abyste se vyhnuli chybám.

V našem příkladu použijeme název tabulky, i když princip ověření může platit i pro pohledy. Jak se s tím dále vypořádáte, bude jiné.

Dříve jsme k oddělení názvu schématu od názvu tabulky používali PARSENAME. Lze jej také použít, pokud má řetězec název serveru a databáze. Ale v tomto příkladu použijeme pouze názvy schémat a tabulek. Zbytek nechám na vaší brilantní mysli. To bude fungovat bez ohledu na to, zda pojmenujete své tabulky s mezerami nebo bez nich. Mezery v názvech tabulky nebo pohledu jsou platné. Takže to funguje pro dbo.MyFoodCravings nebo [dbo].[My Food Cravings] .

PŘÍKLAD

Pojďme vytvořit tabulku.

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

Poté vytvoříme skript, který bude používat SP_EXECUTESQL. Tím se spustí obecný příkaz DELETE pro libovolnou tabulku s klíčem s 1 sloupcem. První věc, kterou musíte udělat, je analyzovat celý název objektu.

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

Tímto způsobem oddělíme schéma od tabulky. K dalšímu ověření používáme OBJECT_ID. Pokud to není NULL, pak je to platné.

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

Všimněte si také, že jsme použili QUOTENAME. To zajistí, že názvy tabulek s mezerou nespustí chybu tím, že je uzavřete do hranatých závorek.

Ale co takhle ověřit klíčový sloupec? Existenci sloupce cílové tabulky můžete zkontrolovat v 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

Nyní je zde celý scénář toho, čeho chceme dosáhnout.

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

Výsledek tohoto skriptu je na obrázku 7 níže.

Můžete to zkusit s jinými tabulkami. Jednoduše změňte @objekt , @idkey a @id proměnné hodnoty.

Žádné ustanovení pro ladění

Mohou nastat chyby. Takže potřebujete znát vygenerovaný dynamický SQL řetězec, abyste našli hlavní příčinu. Nejsme věštci ani kouzelníci, abychom hádali podobu dynamického řetězce SQL. Takže potřebujete příznak ladění.

Všimněte si na obrázku 7 dříve, že dynamický řetězec SQL je vytištěn na kartě Zprávy v SSMS. Přidali jsme @isDebug proměnnou BIT a v kódu ji nastavte na 1. Když je hodnota 1, vytiskne se dynamický řetězec SQL. To je dobré, pokud potřebujete ladit skript nebo uloženou proceduru, jako je tato. Po dokončení ladění jej nastavte zpět na nulu. Pokud se jedná o uloženou proceduru, nastavte tento příznak jako volitelný parametr s výchozí hodnotou nula.

Chcete-li zobrazit dynamický řetězec SQL, můžete použít 2 možné metody.

  • Pokud je řetězec kratší nebo roven 8000 znakům, použijte PRINT.
  • Nebo použijte SELECT, pokud je řetězec delší než 8000 znaků.

Špatně výkonný dynamický SQL používaný v SP_EXECUTESQL

SP_EXECUTESQL může být pomalý, pokud k němu přiřadíte pomalu běžící dotaz. Doba. To zatím nezahrnuje problémy s čicháním parametrů.

Začněte tedy staticky s kódem, který chcete spouštět dynamicky. Poté zkontrolujte plán provádění a STATISTICS IO. Podívejte se, zda nechybí indexy, které je třeba vytvořit. Nalaďte to brzy.

Sečteno a podtrženo v SP_EXECUTESQL

Používání SP_EXECUTESQL je jako ovládat silnou zbraň. Ale ten, kdo to ovládá, v tom musí být zručný. I když to také není žádná raketová věda. Pokud jste dnes nováčkem, postupem času se to může stát zdravým rozumem.

Tento seznam gotchas není úplný. Pokrývá ale ty běžné. Pokud potřebujete další informace, podívejte se na tyto odkazy:

  • Prokletí a požehnání dynamického SQL, od Erlanda Sommarskoga
  • SP_EXECUTESQL (Transact-SQL), od společnosti Microsoft

Takhle? Pak jej prosím sdílejte na svých oblíbených platformách sociálních médií. Můžete se s námi také podělit o své časem prověřené tipy v sekci Komentáře.


  1. 4 způsoby, jak zkontrolovat, zda tabulka existuje před jejím umístěním na SQL Server (T-SQL)

  2. Co vlastně používá LISTAGG s ORDER BY NULL jako kritérium objednávky?

  3. Jak zkontroluji NLS_LANG klienta?

  4. SQL Server - Nejlepší způsob, jak získat identitu vloženého řádku?