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

10 SP_EXECUTESQL trucchi da evitare per un SQL dinamico migliore

Sai quanto può essere potente uno strumento come SQL dinamico? Usalo nel modo sbagliato e puoi consentire a qualcuno di prendere il controllo del tuo database. Inoltre, potrebbe esserci troppa complessità. Questo articolo ha lo scopo di introdurre le insidie ​​quando si utilizza SP_EXECUTESQL e offre 10 trucchi più comuni da evitare.

SP_EXECUTESQL è uno dei modi in cui è possibile eseguire comandi SQL incorporati in una stringa. Costruisci questa stringa in modo dinamico tramite il codice. Ecco perché chiamiamo questo SQL dinamico. Oltre a una serie di istruzioni, puoi anche passarci un elenco di parametri e valori. In effetti, questi parametri e valori differiscono dal comando EXEC. EXEC non accetta parametri per SQL dinamico. Tuttavia, esegui SP_EXECUTESQL usando EXEC!

Per un principiante dell'SQL dinamico, ecco come invocarlo.

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

Si forma la stringa di comandi che include istruzioni SQL valide. Facoltativamente, puoi passare un elenco di parametri di input o output e i relativi tipi di dati. E infine, passi un elenco di valori separati da virgole. Se si passano i parametri, è necessario passare i valori. Più avanti vedrai esempi giusti e sbagliati di questo mentre continui a leggere.

Utilizzare SP_EXECUTESQL quando non ne hai bisogno

Giusto. Se non ne hai bisogno, non usarlo. Se questo diventa i 10 comandamenti di SP_EXECUTESQL, questo è il primo. È perché questa procedura di sistema può essere facilmente abusata. Ma come fai a saperlo?

Rispondi a questo:c'è un problema se il comando nel tuo SQL dinamico diventa statico? Se non hai niente da dire su questo punto, allora non ne hai bisogno. Vedi l'esempio.

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

Come puoi vedere, l'utilizzo di SP_EXECUTESQL è completo di una stringa di comando, un parametro e un valore. Ma deve essere così? Sicuramente no. Va benissimo avere questo:

DECLARE @productID INT = 1;

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

Ma potrei essere imbarazzato quando vedi gli esempi più avanti nell'articolo. Perché contraddirò ciò che sto affermando in questo primo punto. Vedrai brevi istruzioni SQL dinamiche che sono migliori di quelle statiche. Quindi, abbi pazienza con me perché gli esempi dimostreranno solo i punti delineati qui. Per il resto degli esempi, fai finta per un po' di guardare il codice pensato per SQL dinamico.

Oggetti e variabili fuori campo

L'esecuzione di una serie di comandi SQL in una stringa utilizzando SP_EXECUTESQL è come creare una stored procedure senza nome ed eseguirla. Sapendo questo, oggetti come tabelle temporanee e variabili al di fuori della stringa di comando non saranno inclusi nell'ambito. Per questo motivo, si verificheranno errori di runtime.

Quando si utilizzano variabili SQL

Dai un'occhiata.

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

La variabile @extraText è invisibile ai comandi eseguiti. Invece di questo, una stringa letterale o la variabile dichiarata all'interno della stringa SQL dinamica è molto meglio. Ad ogni modo, il risultato è:

Hai visto quell'errore nella Figura 1? Se devi passare un valore all'interno della stringa SQL dinamica, aggiungi un altro parametro.

Quando si utilizzano tabelle temporanee

Nell'ambito di un modulo esistono anche tabelle temporanee in SQL Server. Allora, cosa ne pensi di questo codice?

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

Il codice sopra sta eseguendo 2 stored procedure in successione. La tabella temporanea creata dal primo SQL dinamico non sarà accessibile al secondo. Di conseguenza, otterrai un Nome oggetto non valido #TempNames errore.

Errore di QUOTENAME di SQL Server

Ecco un altro esempio che sembrerà a prima vista ma causerà un errore di nome oggetto non valido. Vedi il codice qui sotto.

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

Per essere valido per SELECT, racchiudere lo schema e la tabella tra parentesi quadre come questa:[Schema].[Tabella] . Oppure non racchiuderli affatto (a meno che il nome della tabella non includa uno o più spazi). Nell'esempio precedente, non sono state utilizzate parentesi quadre sia per la tabella che per lo schema. Invece di usare @Table come parametro, è diventato un segnaposto per REPLACE. È stato utilizzato il QUOTENAME.

QUOTENAME racchiude una stringa con un delimitatore. Questo è utile anche per gestire le virgolette singole nella stringa SQL dinamica. La parentesi quadra è il delimitatore predefinito. Quindi, nell'esempio sopra, cosa pensi che abbia fatto QUOTENAME? Controlla la Figura 2 di seguito.

L'istruzione SQL PRINT ci ha aiutato a eseguire il debug del problema stampando la stringa SQL dinamica. Ora conosciamo il problema. Qual è il modo migliore per risolvere questo problema? Una soluzione è il codice seguente:

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

Spieghiamo questo.

Innanzitutto, @Table viene utilizzato come segnaposto per REPLACE. Cercherà l'occorrenza di @Table e sostituirlo con i valori corretti. Come mai? Se viene utilizzato come parametro, si verificherà un errore. Questo è anche il motivo per l'utilizzo di REPLACE.

Quindi, abbiamo usato PARSENAME. La stringa che abbiamo passato a questa funzione la separerà in schema e tabella. PARSENAME(@tableName,2) otterrà lo schema. Mentre PARSENAME(@tableName,1) otterrà il tavolo.

Infine, QUOTENAME racchiuderà lo schema e i nomi delle tabelle separatamente dopo aver eseguito PARSENAME. Il risultato è [Persona].[Persona] . Ora è valido.

Tuttavia, in seguito verrà mostrato un modo migliore per disinfettare la stringa SQL dinamica con i nomi degli oggetti.

Esecuzione di SP_EXECUTESQL con un'istruzione NULL

Ci sono giorni in cui sei arrabbiato o scoraggiato. Gli errori possono essere commessi lungo la strada. Quindi aggiungi una lunga stringa SQL dinamica al mix e ai NULL. E il risultato?

Niente.

SP_EXECUTESQL ti darà un risultato vuoto. Come? Concatenando un NULL per errore. Considera l'esempio seguente:

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

All'inizio, il codice sembra buono. Eppure gli occhi d'aquila tra noi noteranno il @crlf variabile. Il suo valore? La variabile non è stata inizializzata. Quindi, è NULL.

Ma qual è il punto di quella variabile? In una sezione successiva, saprai quanto è importante per la formattazione e il debug. Per ora, concentriamoci sul punto in questione.

Innanzitutto, la concatenazione di una variabile NULL alla stringa SQL dinamica risulterà in NULL. Quindi, PRINT stamperà in bianco. Infine, SP_EXECUTESQL funzionerà correttamente con la stringa SQL dinamica NULL. Ma non restituisce nulla.

I NULL possono ipnotizzarci in una giornata già brutta. Fai una breve pausa. Relax. Poi torna con la mente più chiara.

Inlineare i valori dei parametri

Disordinato.

Ecco come appariranno i valori incorporati nella stringa SQL dinamica. Ci saranno molte virgolette singole per stringhe e date. Se non stai attento, anche O'Brien e O'Neil causeranno errori. E poiché l'SQL dinamico è una stringa, devi CONVERTIRE o CAST i valori in stringa. Ecco un esempio.

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;

Ho visto stringhe dinamiche più disordinate di questa. Notare le virgolette singole, CONVERT e CAST. Se si utilizzassero i parametri, questo potrebbe avere un aspetto migliore. Puoi vederlo qui sotto

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

Vedere? Meno virgolette singole, niente CONVERT e CAST e anche più pulito.

Ma c'è un effetto collaterale ancora più pericoloso dei valori inline.

Iniezione SQL

Se vivessimo in un mondo in cui tutte le persone fossero buone, l'iniezione SQL non verrebbe mai considerata. Ma non è così. Qualcuno potrebbe iniettare codice SQL dannoso nel tuo. Come può accadere?

Ecco lo scenario che useremo nel nostro esempio:

  • I valori sono fusi con la stringa SQL dinamica come nel nostro esempio precedente. Nessun parametro.
  • La stringa SQL dinamica è fino a 2 GB utilizzando NVARCHAR(MAX). Molto spazio per iniettare codice dannoso.
  • Peggio ancora, la stringa SQL dinamica viene eseguita con autorizzazioni elevate.
  • L'istanza di SQL Server accetta l'autenticazione SQL.

È troppo? Per i sistemi gestiti da un solo uomo, questo può accadere. Nessuno lo controlla comunque. Per le aziende più grandi, a volte esiste un reparto IT poco attento alla sicurezza.

È ancora una minaccia oggi? Lo è, secondo il provider di servizi cloud Akamai nel rapporto sullo stato di Internet/sulla sicurezza. Tra novembre 2017 e marzo 2019, SQL injection rappresenta quasi i due terzi di tutti gli attacchi alle applicazioni web. Questa è la più alta di tutte le minacce esaminate. Molto male.

Vuoi vederlo di persona?

Pratica di SQL injection: pessimo esempio

Facciamo un po' di SQL injection in questo esempio. Puoi provarlo nel tuo AdventureWorks Banca dati. Ma assicurati che l'autenticazione SQL sia consentita ed eseguila con autorizzazioni elevate.

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

Il codice sopra non rappresenta il codice effettivo di un'azienda esistente. Non è nemmeno richiamabile da un'app. Ma questo illustra l'atto malvagio. Allora, cosa abbiamo qui?

Innanzitutto, il codice inserito creerà un account SQL che assomiglia a sa , ma non lo è. E come sa , questo ha amministratore di sistema permessi. Alla fine, questo verrà utilizzato per accedere al database in qualsiasi momento con privilegi completi. Tutto è possibile una volta fatto:furto, cancellazione, manomissione di dati aziendali, e così via.

Questo codice verrà eseguito? Decisamente! E una volta fatto, il super account verrà creato silenziosamente. E, naturalmente, l'indirizzo di Zheng Mu apparirà nel set di risultati. Tutto il resto è normale. Shady, non credi?

Dopo aver eseguito il codice sopra, prova a eseguire anche questo:

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

Se restituisce 1, è dentro, chiunque questo è. In alternativa, puoi verificarlo negli accessi di sicurezza di SQL Server in SQL Server Management Studio.

Allora, cosa hai ottenuto?

Spaventoso, vero? (Se questo è reale, lo è.)

Pratica di SQL injection:buon esempio

Ora, cambiamo un po' il codice usando i parametri. Le altre condizioni sono sempre le stesse.

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

Ci sono 2 differenze nel risultato rispetto al primo esempio.

  • In primo luogo, l'indirizzo di Zheng Mu non apparirà. Il set di risultati è vuoto.
  • Quindi, l'account rinnegato non viene creato. L'utilizzo di IS_SRVROLEMEMBER restituirà NULL.

Cosa è successo?

Poiché vengono utilizzati parametri, il valore di @firstName è 'Zheng"; CREA LOGIN sà CON PASSWORD="12345"; ALT' . Viene preso come valore letterale e troncato a soli 50 caratteri. Controllare il parametro del nome nel codice sopra. È NVARCHAR(50). Ecco perché il set di risultati è vuoto. Nessuna persona con un nome del genere è nel database.

Questo è solo un esempio di SQL injection e un modo per evitarlo. C'è più coinvolto nel fare cose reali. Ma spero di aver chiarito il motivo per cui i valori inline in SQL dinamico sono cattivi.

Sniffing dei parametri

Hai riscontrato una procedura memorizzata a esecuzione lenta da un'app, ma quando hai provato a eseguirla in SSMS è diventata veloce? È sconcertante perché hai utilizzato i valori esatti dei parametri utilizzati nell'app.

Questo è lo sniffing dei parametri in azione. SQL Server crea un piano di esecuzione la prima volta che viene eseguita o ricompilata la stored procedure. Quindi, riutilizza il piano per la corsa successiva. Sembra fantastico perché SQL Server non ha bisogno di ricreare il piano ogni volta. Ma a volte un valore di parametro diverso richiede un piano diverso per funzionare velocemente.

Ecco una dimostrazione usando SP_EXECUTESQL e un semplice SQL statico.

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

Questo è molto semplice. Controllare il piano di esecuzione nella Figura 3.

Ora proviamo la stessa query utilizzando SQL statico.

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

Dai un'occhiata alla Figura 4, quindi confrontala con la Figura 3.

Nella Figura 3, Ricerca dell'indice e Ciclo annidato vengono utilizzati. Ma nella Figura 4, è una Scansione indice cluster . Anche se a questo punto non vi è alcuna penalizzazione delle prestazioni distinguibile, ciò dimostra che lo sniffing dei parametri non è solo un'immaginazione.

Questo può essere molto frustrante una volta che la query diventa lenta. Potresti finire per usare tecniche come la ricompilazione o l'utilizzo di suggerimenti per le query per evitarlo. Ognuno di questi ha degli svantaggi.

Stringa SQL dinamica non formattata in SP_EXECUTESQL

Cosa può andare storto con questo codice?

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

È breve e semplice. Ma controlla la Figura 5 di seguito.

Gli errori si verificano se non ti dispiace un singolo spazio tra le parole chiave e gli oggetti durante la formazione della stringa SQL dinamica. Come nella Figura 5, dove ProductCount l'alias di colonna e la parola chiave FROM non hanno spazi intermedi. Diventa confuso quando una parte di una stringa scorre fino alla riga di codice successiva. Ti fa pensare che la sintassi sia corretta.

Si noti inoltre che la stringa utilizzava 2 righe nella finestra del codice, ma l'output di PRINT mostra 1 riga. Immagina se questa è una stringa di comando molto, molto lunga. È difficile trovare il problema finché non formatti correttamente la stringa dalla scheda Messaggi.

Per risolvere questo problema, aggiungi un ritorno a capo e un avanzamento riga. Probabilmente noterai una variabile @crlf dagli esempi precedenti. La formattazione della stringa SQL dinamica con spazio e una nuova riga renderà la stringa SQL dinamica più leggibile. Questo è ottimo anche per il debug.

Considera un'istruzione SELECT con JOIN. Sono necessarie diverse righe di codice come nell'esempio seguente.

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

Per formattare la stringa, @crlf La variabile è impostata su NCHAR(13), un ritorno a capo, e NCHAR(10), un avanzamento riga. È concatenato a ciascuna riga per interrompere una lunga stringa di istruzioni SELECT. Per vedere il risultato nella scheda Messaggi, utilizziamo PRINT. Controllare l'output nella Figura 6 di seguito.

Il modo in cui si forma la stringa SQL dinamica dipende da te. Qualunque cosa ti si addice per renderlo chiaro, leggibile e facile da eseguire il debug quando arriva il momento.

Nomi di oggetti non disinfettati

Per qualsiasi motivo è necessario impostare dinamicamente i nomi di tabelle, viste o database? Quindi è necessario "disinfettare" o convalidare questi nomi di oggetti per evitare errori.

Nel nostro esempio utilizzeremo il nome di una tabella, sebbene il principio di convalida possa essere applicato anche alle viste. Il modo in cui lo affronterai in seguito sarà diverso.

In precedenza, utilizziamo PARSENAME per separare il nome dello schema dal nome della tabella. Può essere utilizzato anche se la stringa ha un nome di server e database. Ma in questo esempio useremo solo nomi di schemi e tabelle. Lascio il resto alle vostre menti brillanti. Funzionerà indipendentemente dal nome delle tabelle con o senza spazi. Gli spazi sui nomi di tabelle o viste sono validi. Quindi, funziona per dbo.MyFoodCravings o [dbo].[Le mie voglie di cibo] .

ESEMPIO

Creiamo una tabella.

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

Quindi creiamo uno script che utilizzerà SP_EXECUTESQL. Questo eseguirà un'istruzione DELETE generica per qualsiasi tabella data una chiave a 1 colonna. La prima cosa da fare è analizzare il nome completo dell'oggetto.

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

In questo modo separiamo lo schema dalla tabella. Per convalidare ulteriormente, utilizziamo OBJECT_ID. Se non è NULL, allora è valido.

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

Si noti inoltre che abbiamo utilizzato QUOTENAME. Ciò assicurerà che i nomi delle tabelle con spazio non generino un errore racchiudendoli tra parentesi quadre.

Ma che ne dici di convalidare la colonna chiave? Puoi verificare l'esistenza della colonna della tabella di destinazione in sys.columns .

IF (SELECT COUNT(*) FROM sys.columns
	    WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
		  AND [name] = @idKey) > 0
BEGIN
     -- add miscellaneous code here, if needed
     EXEC sp_executesql @sql, @paramsList, @id
END
ELSE
BEGIN
     PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
     -- if you need to do anything else, insert it here
END

Ora, ecco lo script completo di ciò che vogliamo realizzare.

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

Il risultato di questo script è nella Figura 7 di seguito.

Puoi provare questo con altri tavoli. Basta cambiare @object , @idkey e @id valori variabili.

Nessuna disposizione per il debug

Possono verificarsi errori. Quindi, è necessario conoscere la stringa SQL dinamica generata per trovare la causa principale. Non siamo indovini o maghi per indovinare la forma della stringa SQL dinamica. Quindi, hai bisogno di un flag di debug.

Si noti nella Figura 7 in precedenza che la stringa SQL dinamica viene stampata nella scheda Messaggi di SSMS. Abbiamo aggiunto un @isDebug BIT e impostarlo su 1 nel codice. Quando il valore è 1, verrà stampata la stringa SQL dinamica. Questo è utile se è necessario eseguire il debug di uno script o di una procedura memorizzata come questa. Basta riportarlo a zero quando hai terminato il debug. Se si tratta di una procedura memorizzata, imposta questo flag come parametro facoltativo con un valore predefinito pari a zero.

Per vedere la stringa SQL dinamica, puoi usare 2 metodi possibili.

  • Utilizzare PRINT se la stringa è inferiore o uguale a 8000 caratteri.
  • Oppure usa SELECT se la stringa contiene più di 8000 caratteri.

SQL dinamico con prestazioni scadenti utilizzato in SP_EXECUTESQL

SP_EXECUTESQL può essere lento se gli si assegna una query a esecuzione lenta. Periodo. Ciò non comporta ancora problemi con lo sniffing dei parametri.

Quindi, inizia statico con il codice che desideri eseguire in modo dinamico. Quindi, controlla il Piano di esecuzione e STATISTICS IO. Verifica se mancano gli indici che devi creare. Accordalo in anticipo.

Il risultato finale in SP_EXECUTESQL

Usare SP_EXECUTESQL è come impugnare un'arma potente. Ma chi lo esercita deve essere abile. Anche se anche questa non è scienza missilistica. Se sei un principiante oggi, può diventare buon senso col tempo con la pratica.

Questo elenco di trucchi non è completo. Ma copre quelli comuni. Se hai bisogno di maggiori informazioni, controlla questi link:

  • La maledizione e le benedizioni di Dynamic SQL, di Erland Sommarskog
  • SP_EXECUTESQL (Transact-SQL), di Microsoft

Come questo? Quindi condividilo sulle tue piattaforme di social media preferite. Puoi anche condividere con noi i tuoi suggerimenti collaudati nella sezione Commenti.