sql >> データベース >  >> RDS >> Sqlserver

SQLServerでの動的SQL実行

    動的SQLは、実行時に構築および実行されるステートメントであり、通常、動的に生成されたSQL文字列部分、入力パラメーター、またはその両方が含まれます。

    動的に生成されたSQLコマンドを作成して実行するには、さまざまな方法を使用できます。現在の記事では、それらを調査し、それらのプラス面とマイナス面を定義し、いくつかの頻繁なシナリオでクエリを最適化するための実用的なアプローチを示します。

    動的SQLを実行するには、次の2つの方法を使用します。 EXEC コマンドとsp_executesql ストアドプロシージャ。

    EXEC/EXECUTEコマンドの使用

    最初の例では、 AdventureWorksから単純な動的SQLステートメントを作成します。 データベース。この例には、連結された文字列変数@AddressPartを通過し、最後のコマンドで実行される1つのフィルターがあります。

    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)
    

    文字列連結によって構築されたクエリは、SQLインジェクションの脆弱性をもたらす可能性があることに注意してください。このトピックに精通することを強くお勧めします。この種の開発アーキテクチャを使用することを計画している場合、特に公開されているWebアプリケーションで使用する場合は、非常に便利です。

    次に、文字列連結のNULL値を処理する必要があります 。たとえば、前の例の@AddressPartインスタンス変数は、この値が渡された場合、SQLステートメント全体を無効にする可能性があります。

    この潜在的な問題を処理する最も簡単な方法は、ISNULL関数を使用して有効なSQLステートメントを作成することです

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

    重要! EXECコマンドは、キャッシュされた実行プランを再利用するようには設計されていません。実行ごとに新しいものが作成されます。

    これを示すために、同じクエリを2回実行しますが、入力パラメータの値は異なります。次に、両方の場合の実行プランを比較します。

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

    拡張プロシージャsp_executesqlの使用

    このプロシージャを使用するには、SQLステートメント、そこで使用されるパラメータの定義、およびそれらの値を指定する必要があります。構文は次のとおりです。

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

    ステートメントとパラメータを渡す方法を示す簡単な例から始めましょう:

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

    EXECコマンドとは異なり、 sp_executesql 拡張ストアドプロシージャは、同じステートメントで異なるパラメータを使用して実行された場合、実行プランを再利用します。したがって、 sp_executesqlを使用することをお勧めします。 EXEC以上 コマンド

    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

    これまで、スクリプトで動的SQLを使用していました。ただし、これらの構成をカスタムプログラミングオブジェクト(ユーザーストアドプロシージャ)で実行すると、実際のメリットが明らかになります。

    さまざまな入力プロシージャパラメータ値に基づいて、AdventureWorksデータベースで人を検索するプロシージャを作成しましょう。ユーザー入力から、動的SQLコマンドを作成して実行し、呼び出し元のユーザーアプリケーションに結果を返します。

    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
    

    sp_executesqlのOUTPUTパラメータ

    sp_executesqlを使用できます OUTPUTパラメータを使用して、SELECTステートメントによって返される値を保存します。以下の例に示すように、これはクエリによって出力変数@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
    

    sp_executesqlプロシージャによるSQLインジェクションに対する保護

    SQLインジェクションのリスクを大幅に減らすために実行する必要がある2つの簡単なアクティビティがあります。まず、テーブル名を角かっこで囲みます。次に、データベースにテーブルが存在するかどうかをコードでチェックインします。これらの方法は両方とも、以下の例に示されています。

    単純なストアドプロシージャを作成し、有効なパラメータと無効なパラメータを使用して実行しています:

    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'

    EXECコマンドとsp_executesqlストアドプロシージャの機能比較

    EXECコマンド sp_executesqlストアドプロシージャ
    キャッシュプランの再利用なし キャッシュプランの再利用
    SQLインジェクションに対して非常に脆弱です SQLインジェクションに対する脆弱性がはるかに少ない
    出力変数なし 出力変数をサポートします
    パラメータ化なし パラメータ化をサポート

    結論

    この投稿では、SQLServerに動的SQL機能を実装する2つの方法を示しました。 sp_executesqlを使用する方がよい理由を学びました 利用可能な場合は手順。また、EXECコマンドを使用する際の特異性と、SQLインジェクションを防ぐためにユーザー入力をサニタイズする必要があることを明確にしました。

    SQL Server Management Studio v18(およびそれ以降)でのストアドプロシージャの正確で快適なデバッグには、人気のあるdbForgeSQLCompleteソリューションの一部である専用のT-SQLデバッガー機能を使用できます。


    1. MySQLでのPOSITION()関数のしくみ

    2. PostgreSQLのクラスター間レプリケーションを構成する方法

    3. isqlで完全なエラーメッセージを取得する

    4. MariaDBで利用可能な文字セットを取得する2つの方法