Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

10 SP_EXECUTESQL Трябва да избягвате за по-добър динамичен SQL

Знаете ли колко мощен може да бъде инструмент като динамичния SQL? Използвайте го по грешен начин и можете да позволите на някой да поеме вашата база данни. Освен това може да има твърде много сложност. Тази статия има за цел да представи клопките при използване на SP_EXECUTESQL и предлага 10 най-често срещани проблеми, които трябва да избягвате.

SP_EXECUTESQL е един от начините, по които можете да изпълнявате SQL команди, вградени в низ. Вие изграждате този низ динамично чрез кода. Ето защо ние наричаме този динамичен SQL. Освен поредица от изрази, можете също да предадете в него списък с параметри и стойности. Всъщност тези параметри и стойности се различават от командата EXEC. EXEC не приема параметри за динамичен SQL. И все пак, вие изпълнявате SP_EXECUTESQL с помощта на EXEC!

За начинаещ в динамичния SQL, ето как да извикате това.

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

Вие формирате низа от команди, които включват валидни SQL изрази. По избор можете да предадете списък с входни или изходни параметри и техните типове данни. И накрая, вие предавате разделен със запетая списък със стойности. Ако предавате параметри, трябва да предавате стойности. По-късно ще видите правилни и грешни примери за това, докато четете.

Използване на SP_EXECUTESQL, когато не ви трябва

Това е вярно. Ако нямате нужда от него, не го използвайте. Ако това стане 10-те заповеди на SP_EXECUTESQL, това е първата. Това е така, защото тази системна процедура може лесно да бъде злоупотребена. Но откъде знаеш?

Отговорете на това:Има ли проблем, ако командата във вашия динамичен SQL стане статична? Ако нямате какво да кажете по този въпрос, значи нямате нужда от него. Вижте примера.

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

Както можете да видите, използването на SP_EXECUTESQL е завършено с команден низ, параметър и стойност. Но трябва ли да е така? Със сигурност не. Съвсем добре е да имате това:

DECLARE @productID INT = 1;

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

Но може да се срамувам, когато видите примерите по-нататък в статията. Защото ще противореча на това, което твърдя в тази първа точка. Ще видите кратки динамични SQL изрази, които са по-добри от статичните. Така че, имайте търпение, защото примерите само ще докажат точките, очертани тук. За останалите примери се преструвайте за известно време, че разглеждате кода, предназначен за динамичен SQL.

Обекти и променливи извън обхвата

Изпълнението на поредица от SQL команди в низ с помощта на SP_EXECUTESQL е като да създадете безименна съхранена процедура и да я стартирате. Знаейки това, обекти като временни таблици и променливи извън командния низ ще бъдат извън обхвата. Поради това ще възникнат грешки по време на изпълнение.

При използване на SQL променливи

Вижте това.

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

Променливата @extraText е невидим за изпълнените команди. Вместо това литерален низ или променливата, декларирана в динамичния SQL низ, е много по-добра. Както и да е, резултатът е:

Видяхте ли тази грешка на фигура 1? Ако трябва да предадете стойност в динамичния SQL низ, добавете друг параметър.

При използване на временни таблици

Временни таблици в SQL Server също съществуват в обхвата на модул. И така, какво мислите за този код?

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

Кодът по-горе изпълнява 2 съхранени процедури последователно. Временната таблица, създадена от първия динамичен SQL, няма да бъде достъпна за втория. В резултат на това ще получите Невалидно име на обект #TempNames грешка.

Грешка QUOTENAME на SQL сървъра

Ето още един пример, който ще изглежда добре на пръв поглед, но ще причини грешка с невалидно име на обект. Вижте кода по-долу.

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

За да бъде валиден за SELECT, оградете схемата и таблицата с квадратни скоби по следния начин:[Схема].[Таблица] . Или изобщо не ги затваряйте (освен ако името на таблицата не включва едно или повече интервали). В примера по-горе не са използвани квадратни скоби както за таблицата, така и за схемата. Вместо да използвате @Table като параметър, той се превърна в заместител за REPLACE. QUOTENAME беше използван.

QUOTENAME обхваща низ с разделител. Това е добре и за справяне с единични кавички в динамичния SQL низ. Квадратната скоба е разделител по подразбиране. И така, в примера по-горе, какво мислите, че направи QUOTENAME? Проверете фигура 2 по-долу.

Инструкцията SQL PRINT ни помогна да отстраним проблема чрез отпечатване на динамичния SQL низ. Сега знаем проблема. Какъв е най-добрият начин да поправите това? Едно решение е кодът по-долу:

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

Нека обясним това.

Първо, @Table се използва като заместител за REPLACE. Той ще търси появата на @Table и го заменете с правилните стойности. Защо? Ако това се използва като параметър, ще възникне грешка. Това е и причината да използвате REPLACE.

След това използвахме PARSENAME. Низът, който сме предали на тази функция, ще го раздели на схема и таблица. PARSENAME(@tableName,2) ще получи схемата. Докато PARSENAME(@tableName,1) ще получи масата.

И накрая, QUOTENAME ще обхване имената на схемата и таблицата поотделно, след като PARSENAME приключи. Резултатът е [Лице].[Лице] . Сега е валидно.

Въпреки това, по-добър начин за саниране на динамичния SQL низ с имена на обекти ще бъде показан по-късно.

Изпълнение на SP_EXECUTESQL с оператор NULL

Има дни, в които сте разстроени или унили. По пътя могат да се правят грешки. След това добавете дълъг динамичен SQL низ към микса и NULL. А резултатът?

Нищо.

SP_EXECUTESQL ще ви даде празен резултат. Как? Чрез конкатениране на NULL по погрешка. Помислете за примера по-долу:

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

Първоначално кодът изглежда добре. И все пак орловите очи сред нас ще забележат @crlf променлива. Неговата стойност? Променливата не е инициализирана. И така, това е NULL.

Но какъв е смисълът на тази променлива? В следващ раздел ще разберете колко важно е форматирането и отстраняването на грешки. Засега нека се съсредоточим върху разглежданата точка.

Първо, конкатенирането на променлива NULL към динамичния SQL низ ще доведе до NULL. След това PRINT ще отпечата празно. И накрая, SP_EXECUTESQL ще работи добре с динамичния SQL низ NULL. Но не връща нищо.

NULL могат да ни хипнотизират в вече лош ден. Направете кратка почивка. Отпуснете се. След това се върнете с по-ясен ум.

Вградени стойности на параметри

Разхвърлян.

Ето как ще изглежда вграждането на стойности в динамичен SQL низ. Ще има много единични кавички за низове и дати. Ако не внимавате, O’Briens и O’Neils също ще причинят грешки. И тъй като динамичният SQL е низ, трябва да КОНВЕРТИРАТЕ или КАСТ стойностите към низ. Ето един пример.

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;

Видях по-объркани динамични струни от тази. Обърнете внимание на единичните кавички, CONVERT и CAST. Ако се използват параметри, това може да изглежда по-добре. Можете да го видите по-долу

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

Виждаш ли? По-малко единични кавички, без CONVERT и CAST и също по-чисто.

Но има още по-опасен страничен ефект от вградените стойности.

SQL инжекция

Ако живеехме в свят, в който всички хора бяха добри, SQL инжекцията никога нямаше да се мисли. Но това не е така. Някой може да инжектира злонамерен SQL код във вашия. Как може да се случи това?

Ето сценария, който ще използваме в нашия пример:

  • Стойностите се обединяват с динамичния SQL низ като нашия пример по-рано. Без параметри.
  • Динамичният SQL низ е до 2GB, използвайки NVARCHAR(MAX). Много място за инжектиране на злонамерен код.
  • Най-лошото е, че динамичният SQL низ се изпълнява с повишени разрешения.
  • Екземплярът на SQL Server приема SQL удостоверяване.

Това твърде много ли е? За системи, управлявани от един човек, това може да се случи. Така или иначе никой не го проверява. За по-големите компании понякога съществува слаб ИТ отдел със сигурност.

Това все още ли е заплаха днес? Това е според доставчика на облачни услуги Akamai в техния доклад за състоянието на интернет/сигурността. Между ноември 2017 г. и март 2019 г. SQL инжектирането представлява почти две трети от всички атаки на уеб приложения. Това е най-високата от всички разгледани заплахи. Много лошо.

Искате ли да го видите сами?

Практика за инжектиране на SQL: Лош пример

Нека направим малко SQL инжекция в този пример. Можете да опитате това във вашия собствен AdventureWorks база данни. Но се уверете, че SQL удостоверяването е разрешено и го изпълнявате с повишени разрешения.

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

Кодът по-горе не представлява действителен код от съществуваща компания. Дори не се извиква от приложение. Но това илюстрира злото дело. И така, какво имаме тук?

Първо, инжектираният код ще създаде SQL акаунт, който изглежда като sa , но не е. И като sa , това има sysadmin разрешения. В крайна сметка това ще се използва за достъп до базата данни по всяко време с пълни привилегии. Всичко е възможно, след като това е направено:кражба, изтриване, подправяне на корпоративни данни, вие го наречете.

Ще работи ли този код? Определено! И след като стане, супер акаунтът ще бъде създаден безшумно. И, разбира се, адресът на Джън Му ще се появи в набора от резултати. Всичко друго е нормално. Шади, не мислиш ли?

След като изпълните горния код, опитайте да изпълните и това:

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

Ако върне 1, той е вътре, който и да е е Като алтернатива можете да го проверите в входовете за сигурност на вашия SQL Server в SQL Server Management Studio.

И така, какво получи?

Страшно, нали? (Ако това е реално, така е.)

Практика за инжектиране на SQL:добър пример

Сега нека променим малко кода с помощта на параметри. Другите условия остават същите.

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

Има 2 разлики в резултата в сравнение с първия пример.

  • Първо, адресът на Джън Му няма да се показва. Резултатът е празен.
  • След това акаунтът на отстъпника не се създава. Използването на IS_SRVROLEMEMBER ще върне NULL.

Какво се случи?

Тъй като се използват параметри, стойността на @firstName е „Джън“; Създайте ВХОД с ПАРОЛА=”12345”; ALT’ . Това се приема като буквална стойност и се съкращава само до 50 знака. Проверете параметъра за първото име в кода по-горе. Това е NVARCHAR(50). Ето защо наборът от резултати е празен. Няма лице с такова име в базата данни.

Това е само един пример за SQL инжектиране и един от начините да го избегнете. Има повече участие в правенето на реални неща. Но се надявам, че съм изяснил идеята защо вградените стойности в динамичния SQL са лоши.

Подушване на параметри

Изпитвали ли сте бавно работеща съхранена процедура от приложение, но когато се опитахте да я стартирате в SSMS, тя стана бързо? Това е объркващо, защото сте използвали точните стойности на параметрите, използвани в приложението.

Това е подслушване на параметри в действие. SQL Server създава план за изпълнение при първия път, когато съхранената процедура се изпълнява или прекомпилира. След това използвайте повторно плана за следващото изпълнение. Това звучи страхотно, защото SQL Server не трябва да пресъздава плана всеки път. Но има моменти, когато различна стойност на параметъра се нуждае от различен план, за да работи бързо.

Ето демонстрация с използване на SP_EXECUTESQL и обикновен статичен 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

Този е много прост. Проверете плана за изпълнение на фигура 3.

Сега нека опитаме същата заявка, използвайки статичен SQL.

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

Вижте Фигура 4, след което я сравнете с Фигура 3.

На фигура 3 Търсене в индекс и Вложен цикъл са използвани. Но на фигура 4 това е Клъстерно индексно сканиране . Въпреки че в този момент няма забележимо намаление на производителността, това показва, че подслушването на параметри не е просто въображение.

Това може да бъде много разочароващо, след като заявката стане бавна. В крайна сметка може да използвате техники като прекомпилиране или използване на подсказки за заявка, за да го избегнете. Всяко от тях има недостатъци.

Неформатиран динамичен SQL низ в SP_EXECUTESQL

Какво може да се обърка с този код?

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

Кратко е и просто. Но проверете фигура 5 по-долу.

Възникват грешки, ако нямате нищо против едно пространство между ключови думи и обекти при формиране на динамичния SQL низ. Както на фигура 5, където ProductCount псевдонимът на колоната и ключовата дума FROM нямат интервал между тях. Става объркващо, след като част от низ тече надолу към следващия ред код. Това ви кара да мислите, че синтаксисът е правилен.

Забележете също, че низът използва 2 реда в кодовия прозорец, но изходът на PRINT показва 1 ред. Представете си, че това е много, много дълъг команден низ. Трудно е да откриете проблема, докато не форматирате правилно низа от раздела Съобщения.

За да разрешите този проблем, добавете връщане на карета и преместване на ред. Вероятно забелязвате променлива @crlf от предишните примери. Форматирането на вашия динамичен SQL низ с интервал и нов ред ще направи динамичния SQL низ по-четлив. Това е чудесно и за отстраняване на грешки.

Помислете за израз SELECT с JOIN. Нуждае се от няколко реда код като примера по-долу.

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

За да форматирате низа, @crlf променливата е зададена на NCHAR(13), връщане на карета и NCHAR(10), превеждане на ред. Той е свързан към всеки ред, за да прекъсне дълъг низ от оператор SELECT. За да видим резултата в раздела Съобщения, използваме PRINT. Проверете изхода на фигура 6 по-долу.

Как ще формирате динамичния SQL низ зависи от вас. Каквото ви подхожда, за да стане ясно, четиво и лесно за отстраняване на грешки, когато му дойде времето.

Неочистени имена на обекти

Имате ли нужда да задавате динамично имена на таблица, изглед или база данни по някаква причина? След това трябва да „дезинфекцирате“ или да потвърдите имената на тези обекти, за да избегнете грешки.

В нашия пример ще използваме име на таблица, въпреки че принципът на валидиране може да се прилага и за изгледи. Как ще се справите с него по-нататък ще бъде различно.

По-рано използвахме PARSENAME, за да отделим името на схемата от името на таблицата. Може да се използва и ако низът има име на сървър и база данни. Но в този пример ще използваме само имена на схеми и таблици. Останалото оставям на вашите блестящи умове. Това ще работи независимо дали наименувате таблиците си със или без интервали. Пространствата в имената на таблица или изглед са валидни. Така че работи за dbo.MyFoodCravings или [dbo].[Моите желания за храна] .

ПРИМЕР

Нека създадем таблица.

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

След това създаваме скрипт, който ще използва SP_EXECUTESQL. Това ще изпълни общ израз DELETE за всяка таблица с ключ от 1 колона. Първото нещо, което трябва да направите, е да анализирате пълното име на обекта.

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

По този начин разделяме схемата от таблицата. За по-нататъшна проверка използваме OBJECT_ID. Ако не е NULL, значи е валидно.

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

Забележете също, че използвахме QUOTENAME. Това ще гарантира, че имената на таблици с интервал няма да предизвикат грешка, като ги оградите с квадратни скоби.

Но какво ще кажете за валидиране на ключовата колона? Можете да проверите съществуването на колоната на целевата таблица в 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

Сега, ето пълния скрипт за това, което искаме да постигнем.

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

Резултатът от този скрипт е на фигура 7 по-долу.

Можете да опитате това с други таблици. Просто променете @object , @idkey и @id променливи стойности.

Няма разпоредба за отстраняване на грешки

Могат да се случат грешки. Така че, трябва да знаете генерирания динамичен SQL низ, за ​​да намерите основната причина. Ние не сме гадатели или магьосници, за да гадаем формата на динамичния SQL низ. И така, имате нужда от флаг за отстраняване на грешки.

Забележете на фигура 7 по-рано, че динамичният SQL низ се отпечатва в раздела Съобщения на SSMS. Добавихме @isDebug BIT променлива и я задайте на 1 в кода. Когато стойността е 1, динамичният SQL низ ще се отпечата. Това е добре, ако трябва да отстраните грешки в скрипт или съхранена процедура като тази. Просто го върнете на нула, когато приключите с отстраняването на грешки. Ако това е съхранена процедура, направете този флаг незадължителен параметър със стойност по подразбиране нула.

За да видите динамичния SQL низ, можете да използвате 2 възможни метода.

  • Използвайте PRINT, ако низът е по-малък или равен на 8000 знака.
  • Или използвайте SELECT, ако низът е повече от 8000 знака.

Слабо представящ се динамичен SQL, използван в SP_EXECUTESQL

SP_EXECUTESQL може да бъде бавен, ако му зададете бавно изпълняваща се заявка. Месечен цикъл. Това все още не включва проблеми с подслушването на параметри.

Така че, започнете статично с кода, който искате да изпълнявате динамично. След това проверете плана за изпълнение и STATISTICS IO. Вижте дали липсват индекси, които трябва да създадете. Настройте го рано.

Дъната линия в SP_EXECUTESQL

Използването на SP_EXECUTESQL е като да притежаваш мощно оръжие. Но този, който го владее, трябва да бъде умел в това. Въпреки че това също не е ракетна наука. Ако днес сте начинаещ, това може да стане здрав разум с времето с практиката.

Този списък с проблеми не е пълен. Но обхваща общите. Ако имате нужда от повече информация, вижте тези връзки:

  • Проклятието и благословиите на динамичния SQL, от Ерланд Сомарског
  • SP_EXECUTESQL (Transact-SQL), от Microsoft

Като този? След това, моля, споделете го в любимите си социални медийни платформи. Можете също да споделите с нас вашите изпитани във времето съвети в секцията за коментари.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 11 най-добри практики за индекс на SQL Server за подобрена настройка на производителността

  2. Как да разбера какво заключва масите ми?

  3. Избягали символи за търсене в пълен текст на SQL Server?

  4. Как да използвате Sum, Avg и Count в Select Statement - SQL Server / TSQL урок, част 128

  5. SQL Server 2017:Налични функции в Linux