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.