Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

Eksekusi SQL Dinamis di SQL Server

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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara menyimpan gambar di kolom tabel database SQL Server

  2. Cara Meningkatkan Ukuran File dari File Data di SQL Server (T-SQL)

  3. Memanggil prosedur tersimpan menggunakan VBA

  4. Tidak dapat memuat DLL 'SqlServerSpatial.dll'

  5. INFORMATION_SCHEMA vs sysobjects