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

Dynamisk SQL-udførelse i SQL Server

Dynamisk SQL er en sætning, der er konstrueret og udført under kørsel, som normalt indeholder dynamisk genererede SQL-strengdele, inputparametre eller begge dele.

Forskellige metoder er tilgængelige til at konstruere og køre dynamisk genererede SQL-kommandoer. Den aktuelle artikel vil udforske dem, definere deres positive og negative aspekter og demonstrere praktiske tilgange til at optimere forespørgsler i nogle hyppige scenarier.

Vi bruger to måder at udføre dynamisk SQL på:EXEC kommando og sp_executesql gemt procedure.

Brug af kommandoen EXEC/EXECUTE

For det første eksempel opretter vi en simpel dynamisk SQL-sætning fra AdventureWorks database. Eksemplet har et filter, der føres gennem den sammenkædede strengvariabel @AddressPart og udføres i den sidste kommando:

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)

Bemærk, at forespørgsler bygget af strengsammenkædning kan give SQL-injektion sårbarheder. Jeg vil kraftigt råde dig til at sætte dig ind i dette emne. Hvis du planlægger at bruge denne form for udviklingsarkitektur, især i en offentligt vendt webapplikation, vil det være mere end nyttigt.

Dernæst bør vi handle er NULL-værdier i strengsammenkædninger . For eksempel kunne @AddressPart-instansvariablen fra det forrige eksempel ugyldiggøre hele SQL-sætningen, hvis den passerede denne værdi.

Den nemmeste måde at håndtere dette potentielle problem på er at bruge ISNULL-funktionen til at konstruere en gyldig SQL-sætning :

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


Vigtig! EXEC-kommandoen er ikke designet til at genbruge cachelagrede eksekveringsplaner! Det vil oprette en ny for hver udførelse.

For at demonstrere dette vil vi udføre den samme forespørgsel to gange, men med en anden værdi af inputparameteren. Derefter sammenligner vi udførelsesplaner i begge tilfælde:

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 *%';

Brug af udvidet procedure sp_executesql

For at bruge denne procedure skal vi give den en SQL-sætning, definitionen af ​​parametre, der bruges i den, og deres værdier. Syntaksen er følgende:

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

Lad os starte med et simpelt eksempel, der viser, hvordan man sender en erklæring og parametre:

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

I modsætning til EXEC-kommandoen er sp_executesql udvidet lagret procedure genbruger eksekveringsplaner, hvis de udføres med samme sætning men forskellige parametre. Derfor er det bedre at bruge sp_executesql over 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 lagrede procedurer

Indtil nu har vi brugt dynamisk SQL i scripts. Men reelle fordele bliver tydelige, når vi udfører disse konstruktioner i brugerdefinerede programmeringsobjekter – brugerlagrede procedurer.

Lad os oprette en procedure, der leder efter en person i AdventureWorks-databasen, baseret på de forskellige input-procedureparameterværdier. Fra brugerinput vil vi konstruere dynamisk SQL-kommando og udføre den for at returnere resultatet til kaldende brugerapplikation:

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 bruge sp_executesql med parameteren OUTPUT for at gemme den værdi, der returneres af SELECT-sætningen. Som vist i eksemplet nedenfor giver dette antallet af rækker, der returneres af forespørgslen til outputvariablen @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

Beskyttelse mod SQL-injektion med sp_executesql-procedure

Der er to enkle aktiviteter, du bør gøre for at reducere risikoen for SQL-injektion markant. Indsæt først tabelnavne i parentes. For det andet skal du kontrollere i koden, om der findes tabeller i databasen. Begge disse metoder er til stede i eksemplet nedenfor.

Vi opretter en simpel lagret procedure og udfører den med gyldige og ugyldige parametre:

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'

Funktionssammenligning af EXEC Command og sp_executesql Stored Procedure

EXEC-kommando sp_executesql lagret procedure
Ingen genbrug af cacheplan Genbrug af cacheplan
Meget sårbar over for SQL-injektion Meget mindre sårbar over for SQL-injektion
Ingen outputvariable Understøtter outputvariabler
Ingen parametrisering Understøtter parametrisering

Konklusion

Dette indlæg demonstrerede to måder at implementere den dynamiske SQL-funktionalitet i SQL Server. Vi har lært, hvorfor det er bedre at bruge sp_executesql procedure, hvis den er tilgængelig. Vi har også præciseret specificiteten ved at bruge EXEC-kommandoen og kravene til at rense brugerinput for at forhindre SQL-injektion.

For den nøjagtige og behagelige fejlfinding af lagrede procedurer i SQL Server Management Studio v18 (og højere), kan du bruge den specialiserede T-SQL Debugger-funktion, en del af den populære dbForge SQL Complete-løsning.


  1. Forklar Planlæg Cost Pain Point

  2. Hvordan justify_days() virker i PostgreSQL

  3. Sådan fungerer LEFT() i MariaDB

  4. Tilføj standardværdien for datetime-feltet i SQL Server til et tidsstempel