sql >> Databasteknik >  >> RDS >> Sqlserver

Dynamisk SQL-exekvering i SQL Server

Dynamisk SQL är en sats som konstrueras och exekveras under körning och innehåller vanligtvis dynamiskt genererade SQL-strängdelar, inmatningsparametrar eller båda.

Olika metoder finns tillgängliga för att konstruera och köra dynamiskt genererade SQL-kommandon. Den aktuella artikeln kommer att utforska dem, definiera deras positiva och negativa aspekter och visa praktiska tillvägagångssätt för att optimera frågor i vissa vanliga scenarier.

Vi använder två sätt att köra dynamisk SQL:EXEC kommandot och sp_executesql lagrad procedur.

Använda kommandot EXEC/EXECUTE

För det första exemplet skapar vi en enkel dynamisk SQL-sats från AdventureWorks databas. Exemplet har ett filter som skickas genom den sammanlänkade strängvariabeln @AddressPart och exekveras i det sista kommandot:

USE AdventureWorks2019

-- Declare variable to hold generated SQL statement
DECLARE @SQLExec NVARCHAR(4000) 
DECLARE @AddressPart NVARCHAR(50) = 'a'

-- Build dynamic SQL
SET @SQLExec = 'SELECT * FROM Person.Address WHERE AddressLine1 LIKE ''%' + @AddressPart + '%'''

-- Execute dynamic SQL 
EXEC (@SQLExec)

Observera att frågor byggda av strängsammansättning kan ge SQL-injektion sårbarheter. Jag rekommenderar starkt att du bekantar dig med detta ämne. Om du planerar att använda den här typen av utvecklingsarkitektur, särskilt i en webbapplikation som riktar sig till allmänheten, kommer det att vara mer än användbart.

Därefter bör vi hantera är NULL-värden i strängsammansättningar . Till exempel kan @AddressPart-instansvariabeln från föregående exempel ogiltigförklara hela SQL-satsen om den passerade detta värde.

Det enklaste sättet att hantera detta potentiella problem är att använda ISNULL-funktionen för att konstruera en giltig SQL-sats :

SET @SQLExec = 'SELECT * FROM Person.Address WHERE AddressLine1 LIKE ''%' + ISNULL(@AddressPart, ‘ ‘) + '%'''


Viktig! EXEC-kommandot är inte utformat för att återanvända cachade körningsplaner! Det kommer att skapa en ny för varje körning.

För att visa detta kommer vi att köra samma fråga två gånger, men med ett annat värde på indataparametern. Sedan jämför vi genomförandeplaner i båda fallen:

USE AdventureWorks2019

-- Case 1
DECLARE @SQLExec NVARCHAR(4000) 
DECLARE @AddressPart NVARCHAR(50) = 'a'
 
SET @SQLExec = 'SELECT * FROM Person.Address WHERE AddressLine1 LIKE ''%' + @AddressPart + '%'''

EXEC (@SQLExec)

-- Case 2
SET @AddressPart = 'b'
 
SET @SQLExec = 'SELECT * FROM Person.Address WHERE AddressLine1 LIKE ''%' + @AddressPart + '%'''

EXEC (@SQLExec)

-- Compare plans
SELECT chdpln.objtype
,      chdpln.cacheobjtype
,      chdpln.usecounts
,      sqltxt.text
  FROM sys.dm_exec_cached_plans as chdpln
       CROSS APPLY sys.dm_exec_sql_text(chdpln.plan_handle) as sqltxt
 WHERE sqltxt.text LIKE 'SELECT *%';

Använda utökad procedur sp_executesql

För att använda den här proceduren måste vi ge den en SQL-sats, definitionen av parametrar som används i den och deras värden. Syntaxen är följande:

sp_executesql @SQLStatement, N'@ParamNameDataType' , @Parameter1 = 'Value1'

Låt oss börja med ett enkelt exempel som visar hur man skickar ett uttalande och parametrar:

EXECUTE sp_executesql  
               N'SELECT *  
                     FROM Person.Address
	       WHERE AddressLine1 LIKE ''%'' + @AddressPart + ''%''', -- SQL Statement
              N'@AddressPart NVARCHAR(50)',  -- Parameter definition
             @AddressPart = 'a';  -- Parameter value

Till skillnad från EXEC-kommandot, sp_executesql utökad lagrad procedur återanvänder exekveringsplaner om de körs med samma sats men olika parametrar. Därför är det bättre att använda sp_executesql över EXEC kommando :

EXECUTE sp_executesql  
               N'SELECT *  
                     FROM Person.Address
	       WHERE AddressLine1 LIKE ''%'' + @AddressPart + ''%''', -- SQL Statement
              N'@AddressPart NVARCHAR(50)',  -- Parameter definition
             @AddressPart = 'a';  -- Parameter value

EXECUTE sp_executesql  
               N'SELECT *  
                     FROM Person.Address
	       WHERE AddressLine1 LIKE ''%'' + @AddressPart + ''%''', -- SQL Statement
              N'@AddressPart NVARCHAR(50)',  -- Parameter definition
             @AddressPart = 'b';  -- Parameter value

SELECT chdpln.objtype
,      chdpln.cacheobjtype
,      chdpln.usecounts
,      sqltxt.text
  FROM sys.dm_exec_cached_plans as chdpln
       CROSS APPLY sys.dm_exec_sql_text(chdpln.plan_handle) as sqltxt
  WHERE sqltxt.text LIKE '%Person.Address%';

Dynamisk SQL i lagrade procedurer

Fram till nu har vi använt dynamisk SQL i skript. Men verkliga fördelar blir uppenbara när vi exekverar dessa konstruktioner i anpassade programmeringsobjekt – användarlagrade procedurer.

Låt oss skapa en procedur som kommer att leta efter en person i AdventureWorks-databasen, baserat på de olika inmatningsprocedurernas parametervärden. Från användarinmatningen kommer vi att konstruera ett dynamiskt SQL-kommando och köra det för att returnera resultatet till anropande användarapplikation:

CREATE OR ALTER PROCEDURE [dbo].[test_dynSQL]  
(
  @FirstName		 NVARCHAR(100) = NULL	
 ,@MiddleName        NVARCHAR(100) = NULL	
 ,@LastName			 NVARCHAR(100) = NULL	
)
AS          
BEGIN      
SET NOCOUNT ON;  
 
DECLARE @SQLExec    	NVARCHAR(MAX)
DECLARE @Parameters		NVARCHAR(500)
 
SET @Parameters = '@FirstName NVARCHAR(100),
  		            @MiddleName NVARCHAR(100),
			@LastName NVARCHAR(100)
			'
 
SET @SQLExec = 'SELECT *
	 	           FROM Person.Person
		         WHERE 1 = 1
		        ' 
IF @FirstName IS NOT NULL AND LEN(@FirstName) > 0 
   SET @SQLExec = @SQLExec + ' AND FirstName LIKE ''%'' + @FirstName + ''%'' '

IF @MiddleName IS NOT NULL AND LEN(@MiddleName) > 0 
                SET @SQLExec = @SQLExec + ' AND MiddleName LIKE ''%'' 
                                                                    + @MiddleName + ''%'' '

IF @LastName IS NOT NULL AND LEN(@LastName) > 0 
 SET @SQLExec = @SQLExec + ' AND LastName LIKE ''%'' + @LastName + ''%'' '

EXEC sp_Executesql @SQLExec
	         ,             @Parameters
 , @[email protected],  @[email protected],  
                                                @[email protected]
 
END 
GO

EXEC [dbo].[test_dynSQL] 'Ke', NULL, NULL

OUTPUT-parameter i sp_executesql

Vi kan använda sp_executesql med parametern OUTPUT för att spara värdet som returneras av SELECT-satsen. Som visas i exemplet nedan ger detta antalet rader som returneras av frågan till utdatavariabeln @Output:

DECLARE @Output INT

EXECUTE sp_executesql  
        N'SELECT @Output = COUNT(*)
            FROM Person.Address
	       WHERE AddressLine1 LIKE ''%'' + @AddressPart + ''%''', -- SQL Statement
              N'@AddressPart NVARCHAR(50), @Output INT OUT',  -- Parameter definition
             @AddressPart = 'a', @Output = @Output OUT;  -- Parameters

SELECT @Output

Skydd mot SQL-injektion med sp_executesql-procedur

Det finns två enkla aktiviteter du bör göra för att minska risken för SQL-injektion avsevärt. Omge först tabellnamn inom parentes. För det andra, kontrollera i koden om tabeller finns i databasen. Båda dessa metoder finns i exemplet nedan.

Vi skapar en enkel lagrad procedur och exekverar den med giltiga och ogiltiga parametrar:

CREATE OR ALTER PROCEDURE [dbo].[test_dynSQL] 
(
  @InputTableName NVARCHAR(500)
)
AS 
BEGIN 
  DECLARE @AddressPart NVARCHAR(500)
  DECLARE @Output INT
  DECLARE @SQLExec NVARCHAR(1000) 

  IF EXISTS(SELECT 1 FROM sys.objects WHERE type = 'u' AND name = @InputTableName)
  BEGIN

      EXECUTE sp_executesql  
        N'SELECT @Output = COUNT(*)
            FROM Person.Address
	       WHERE AddressLine1 LIKE ''%'' + @AddressPart + ''%''', -- SQL Statement
              N'@AddressPart NVARCHAR(50), @Output INT OUT',  -- Parameter definition
             @AddressPart = 'a', @Output = @Output OUT;  -- Parameters

       SELECT @Output
  END
  ELSE
  BEGIN
     THROW 51000, 'Invalid table name given, possible SQL injection. Exiting procedure', 1 
  END
END


EXEC [dbo].[test_dynSQL] 'Person'
EXEC [dbo].[test_dynSQL] 'NoTable'

Funktionsjämförelse av EXEC Command och sp_executesql lagrad procedur

EXEC-kommando sp_executesql lagrad procedur
Ingen återanvändning av cacheplan Återanvändning av cacheplan
Mycket sårbar för SQL-injektion Mycket mindre sårbar för SQL-injektion
Inga utdatavariabler Stöder utdatavariabler
Ingen parametrisering Stöder parametrisering

Slutsats

Det här inlägget demonstrerade två sätt att implementera den dynamiska SQL-funktionaliteten i SQL Server. Vi har lärt oss varför det är bättre att använda sp_executesql förfarande om det finns tillgängligt. Vi har också klargjort specificiteten för att använda EXEC-kommandot och kraven på att sanera användarinmatningar för att förhindra SQL-injektion.

För korrekt och bekväm felsökning av lagrade procedurer i SQL Server Management Studio v18 (och senare) kan du använda den specialiserade T-SQL Debugger-funktionen, en del av den populära dbForge SQL Complete-lösningen.


  1. Salesforce SOQL från SQL Server

  2. SQLite - Släpp en databas

  3. SQL Server Failover Cluster Installation -3

  4. Hur kan jag hitta icke-ASCII-tecken i MySQL?