SQL Dinamis adalah pernyataan yang dibuat dan dieksekusi saat runtime, biasanya berisi bagian string SQL yang dihasilkan secara dinamis, parameter input, atau keduanya.
Berbagai metode tersedia untuk membangun dan menjalankan perintah SQL yang dihasilkan secara dinamis. Artikel saat ini akan menjelajahinya, mendefinisikan aspek positif dan negatifnya, dan mendemonstrasikan pendekatan praktis untuk mengoptimalkan kueri dalam beberapa skenario yang sering terjadi.
Kami menggunakan dua cara untuk mengeksekusi SQL dinamis:EXEC perintah dan sp_executesql prosedur tersimpan.
Menggunakan Perintah EXEC/EXECUTE
Untuk contoh pertama, kami membuat pernyataan SQL dinamis sederhana dari AdventureWorks basis data. Contoh memiliki satu filter yang dilewatkan melalui variabel string gabungan @AddressPart dan dieksekusi pada perintah terakhir:
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)
Perhatikan bahwa kueri yang dibuat oleh rangkaian string dapat memberikan kerentanan injeksi SQL. Saya sangat menyarankan agar Anda membiasakan diri dengan topik ini. Jika Anda berencana untuk menggunakan arsitektur pengembangan semacam ini, terutama dalam aplikasi web yang menghadap publik, itu akan lebih bermanfaat.
Selanjutnya, kita harus menangani nilai NULL dalam rangkaian string . Misalnya, variabel instans @AddressPart dari contoh sebelumnya dapat membatalkan seluruh pernyataan SQL jika melewati nilai ini.
Cara termudah untuk menangani masalah potensial ini adalah dengan menggunakan fungsi ISNULL untuk membuat pernyataan SQL yang valid :
SET @SQLExec = 'SELECT * FROM Person.Address WHERE AddressLine1 LIKE ''%' + ISNULL(@AddressPart, ‘ ‘) + '%'''
Penting! Perintah EXEC tidak dirancang untuk menggunakan kembali rencana eksekusi yang di-cache! Ini akan membuat yang baru untuk setiap eksekusi.
Untuk mendemonstrasikan ini, kita akan mengeksekusi query yang sama dua kali, tetapi dengan nilai parameter input yang berbeda. Kemudian, kami membandingkan rencana eksekusi dalam kedua kasus:
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 *%';
Menggunakan Prosedur yang Diperpanjang sp_executesql
Untuk menggunakan prosedur ini, kita perlu memberikan pernyataan SQL, definisi parameter yang digunakan di dalamnya, dan nilainya. Sintaksnya adalah sebagai berikut:
sp_executesql @SQLStatement, N'@ParamNameDataType' , @Parameter1 = 'Value1'
Mari kita mulai dengan contoh sederhana yang menunjukkan cara meneruskan pernyataan dan parameter:
EXECUTE sp_executesql
N'SELECT *
FROM Person.Address
WHERE AddressLine1 LIKE ''%'' + @AddressPart + ''%''', -- SQL Statement
N'@AddressPart NVARCHAR(50)', -- Parameter definition
@AddressPart = 'a'; -- Parameter value
Tidak seperti perintah EXEC, sp_executesql prosedur tersimpan diperpanjang menggunakan kembali rencana eksekusi jika dijalankan dengan pernyataan yang sama tetapi parameter yang berbeda. Oleh karena itu, lebih baik menggunakan sp_executesql melalui EXEC perintah :
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%';
SQL Dinamis dalam Prosedur Tersimpan
Sampai sekarang kami menggunakan SQL dinamis dalam skrip. Namun, manfaat nyata menjadi nyata saat kami menjalankan konstruksi ini dalam objek pemrograman khusus – prosedur yang disimpan pengguna.
Mari buat prosedur yang akan mencari seseorang di database AdventureWorks, berdasarkan nilai parameter prosedur input yang berbeda. Dari input pengguna, kami akan membuat perintah SQL dinamis dan menjalankannya untuk mengembalikan hasilnya ke aplikasi pengguna yang memanggil:
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
Parameter OUTPUT di sp_executesql
Kita bisa menggunakan sp_executesql dengan parameter OUTPUT untuk menyimpan nilai yang dikembalikan oleh pernyataan SELECT. Seperti yang ditunjukkan pada contoh di bawah, ini memberikan jumlah baris yang dikembalikan oleh kueri ke variabel output @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
Perlindungan Terhadap Injeksi SQL dengan Prosedur sp_executesql
Ada dua aktivitas sederhana yang harus Anda lakukan untuk mengurangi risiko injeksi SQL secara signifikan. Pertama, lampirkan nama tabel dalam tanda kurung. Kedua, periksa kode apakah tabel ada di database. Kedua metode ini ada dalam contoh di bawah ini.
Kami membuat prosedur tersimpan sederhana dan menjalankannya dengan parameter yang valid dan tidak valid:
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'
Perbandingan Fitur Perintah EXEC dan Prosedur Tersimpan sp_executesql
perintah EXEC | prosedur tersimpan sp_executesql |
Tidak ada paket cache yang digunakan kembali | Penggunaan kembali paket cache |
Sangat rentan terhadap injeksi SQL | Jauh lebih rentan terhadap injeksi SQL |
Tidak ada variabel keluaran | Mendukung variabel keluaran |
Tidak ada parameterisasi | Mendukung parametrisasi |
Kesimpulan
Posting ini menunjukkan dua cara menerapkan fungsionalitas SQL dinamis di SQL Server. Kami telah mempelajari mengapa lebih baik menggunakan sp_executesql prosedur jika tersedia. Selain itu, kami telah mengklarifikasi kekhususan penggunaan perintah EXEC dan tuntutan untuk membersihkan input pengguna untuk mencegah injeksi SQL.
Untuk debugging prosedur tersimpan yang akurat dan nyaman di SQL Server Management Studio v18 (dan lebih tinggi), Anda dapat menggunakan fitur T-SQL Debugger khusus, bagian dari solusi dbForge SQL Complete yang populer.