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

より良い動的SQLのために避けるべき10のSP_EXECUTESQLの落とし穴

    動的SQLのようなツールがどれほど強力であるか知っていますか?間違った方法で使用すると、誰かにデータベースを乗っ取らせることができます。さらに、複雑すぎる可能性があります。この記事は、SP_EXECUTESQLを使用する際の落とし穴を紹介することを目的としており、避けるべき10の最も一般的な落とし穴を提供します。

    SP_EXECUTESQLは、文字列に埋め込まれたSQLコマンドを実行する方法の1つです。この文字列は、コードを介して動的に作成します。これが、この動的SQLと呼ばれる理由です。一連のステートメントとは別に、パラメーターと値のリストを渡すこともできます。実際、これらのパラメータと値はEXECコマンドとは異なります。 EXECは、動的SQLのパラメーターを受け入れません。ただし、EXECを使用してSP_EXECUTESQLを実行します!

    動的SQLの初心者の場合、これを呼び出す方法は次のとおりです。

    EXEC sp_executesql <command string>[, <input or output parameters list>, <parameter value1>, <parameter value n>]

    有効なSQLステートメントを含むコマンドの文字列を作成します。オプションで、入力または出力パラメーターとそれらのデータ型のリストを渡すことができます。最後に、コンマで区切られた値のリストを渡します。パラメータを渡す場合は、値を渡す必要があります。後で読んでいくと、これの正しい例と間違った例の両方が表示されます。

    不要なときにSP_EXECUTESQLを使用する

    それは正しい。必要ない場合は使用しないでください。これがSP_EXECUTESQLの十戒になると、これが最初の戒めになります。これは、このシステム手順が簡単に悪用される可能性があるためです。しかし、どうやって知っていますか?

    これに答えてください:動的SQLのコマンドが静的になった場合に問題はありますか?この点について何も言うことがなければ、それは必要ありません。例を参照してください。

    DECLARE @sql NVARCHAR(100) = N'SELECT ProductID, Name FROM Production.Product ' +
    			      'WHERE ProductID = @ProductID';
    DECLARE @paramsList NVARCHAR(100) = N'@ProductID INT';
    DECLARE @param1Value INT = 1;
    
    EXEC sp_executesql @sql, @paramsList, @param1Value
    GO
    

    ご覧のとおり、SP_EXECUTESQLの使用法は、コマンド文字列、パラメーター、および値を使用して完了します。しかし、それはこのようにする必要がありますか?確かに違います。これがあってもまったく問題ありません:

    DECLARE @productID INT = 1;
    
    SELECT ProductID, Name
    FROM Production.Product
    WHERE ProductID = @productID;
    

    しかし、記事の後半で例を見ると、恥ずかしいかもしれません。私がこの最初の点で主張していることと矛盾するからです。静的よりも優れた短い動的SQLステートメントが表示されます。ですから、例はここで概説されているポイントを証明するだけなので、私に耐えてください。残りの例では、動的SQL用のコードを見ているふりをします。

    範囲外のオブジェクトと変数

    SP_EXECUTESQLを使用して一連のSQLコマンドを文字列で実行することは、名前のないストアドプロシージャを作成して実行することに似ています。これを知っていると、コマンド文字列の外側にある一時テーブルや変数などのオブジェクトは範囲外になります。このため、ランタイムエラーが発生します。

    SQL変数を使用する場合

    これをチェックしてください。

    DECLARE @extraText VARCHAR(10) = 'Name is '; -- note this variable
    DECLARE @sql NVARCHAR(100) = N'SELECT @extraText + FirstName + SPACE(1) + LastName
                                   FROM Person.Person WHERE BusinessEntityID = @BusinessEntityID';
    DECLARE @paramList NVARCHAR(100) = N'@BusinessEntityID INT';
    DECLARE @param1Value INT = 1;
    
    EXEC sp_executesql @sql, @paramList, @BusinessEntityId = @param1Value;
    GO
    

    変数@extraText 実行されたコマンドには表示されません。これの代わりに、リテラル文字列または動的SQL文字列内で宣言された変数の方がはるかに優れています。とにかく、結果は次のとおりです。

    図1でそのエラーを見ましたか?動的SQL文字列内に値を渡す必要がある場合は、別のパラメータを追加します。

    一時テーブルを使用する場合

    SQL Serverの一時テーブルも、モジュールのスコープ内に存在します。では、このコードについてどう思いますか?

    DECLARE @sql NVARCHAR(200) = N'SELECT BusinessEntityID, LastName, FirstName, MiddleName
                                   INTO #TempNames
                                   FROM Person.Person
                                   WHERE BusinessEntityID BETWEEN 1 and 100';
    
    EXEC sp_executesql @sql;
    EXEC sp_executesql N'SELECT * FROM #TempNames'
    GO
    

    上記のコードは、2つのストアドプロシージャを連続して実行しています。最初の動的SQLから作成された一時テーブルは、2番目のSQLにはアクセスできません。その結果、無効なオブジェクト名#TempNamesが取得されます。 エラー。

    SQLServerのQUOTENAMEの間違い

    一見問題ないように見えますが、無効なオブジェクト名エラーが発生する別の例を次に示します。以下のコードを参照してください。

    DECLARE @sql NVARCHAR(100) = N'SELECT * FROM @Table';
    DECLARE @tableName NVARCHAR(20) = 'Person.Person';
    
    SET @sql = REPLACE(@sql,'@Table',QUOTENAME(@tableName));
    
    PRINT @sql;
    EXEC sp_executesql @sql;
    GO
    

    SELECTで有効にするには、スキーマとテーブルを次のように角かっこで囲みます。[Schema]。[Table] 。または、それらをまったく囲まないでください(テーブル名に1つ以上のスペースが含まれている場合を除く)。上記の例では、テーブルとスキーマの両方に角かっこは使用されていません。 @Tableを使用する代わりに パラメータとして、REPLACEのプレースホルダーになりました。 QUOTENAMEが使用されました。

    QUOTENAMEは、文字列を区切り文字で囲みます。これは、動的SQL文字列の単一引用符を処理する場合にも適しています。角括弧はデフォルトの区切り文字です。それで、上記の例では、QUOTENAMEは何をしたと思いますか?下の図2を確認してください。

    SQL PRINTステートメントは、動的SQL文字列を出力することで問題をデバッグするのに役立ちました。これで問題がわかりました。これを修正する最良の方法は何ですか? 1つの解決策は、以下のコードです。

    DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) FROM @Table';
    DECLARE @tableName NVARCHAR(20) = 'Person.Person';
    
    SET @sql = REPLACE(@sql,'@Table',QUOTENAME(PARSENAME(@tableName,2)) + '.'
                                   + QUOTENAME(PARSENAME(@tableName,1)));
    PRINT @sql;
    EXEC sp_executesql @sql;
    GO
    

    これを説明しましょう。

    まず、 @Table REPLACEのプレースホルダーとして使用されます。 @Tableの出現を検索します 正しい値に置き換えてください。なんで?これをパラメータとして使用すると、エラーが発生します。それがREPLACEを使用する理由でもあります。

    次に、PARSENAMEを使用しました。この関数に渡した文字列は、関数をスキーマとテーブルに分離します。 PARSENAME(@ tableName、2) スキーマを取得します。 PARSENAME(@ tableName、1) テーブルを取得します。

    最後に、QUOTENAMEは、PARSENAMEが実行された後、スキーマ名とテーブル名を別々に囲みます。結果は[Person]。[Person] 。現在、有効です。

    ただし、動的SQL文字列をオブジェクト名でサニタイズするためのより良い方法は後で示されます。

    NULLステートメントを使用してSP_EXECUTESQLを実行する

    動揺したり、落ち込んだりする日があります。途中で間違いを犯す可能性があります。次に、長い動的SQL文字列をミックスとNULLに追加します。そしてその結果は?

    何もありません。

    SP_EXECUTESQLは空白の結果を返します。どのように?誤ってNULLを連結することによって。以下の例を検討してください:

    DECLARE @crlf NCHAR(2);
    DECLARE @sql NVARCHAR(200) = N'SELECT' + @crlf +
    	' p.Name AS Product' + @crlf +
    	',v.Name AS Vendor' + @crlf +
    	',v.AccountNumber' + @crlf +
    	',p.ListPrice' + @crlf +
    	'FROM Purchasing.ProductVendor pv' + @crlf +
    	'INNER JOIN Production.Product p ON pv.ProductID = p.ProductID' + @crlf +
    	'INNER JOIN Purchasing.Vendor v ON pv.BusinessEntityID = v.BusinessEntityID' + @crlf +
    	'WHERE pv.BusinessEntityID = @BusinessEntityID';
    DECLARE @paramsList NVARCHAR(100) = N'@BusinessEntityID INT';
    DECLARE @BusinessEntityID INT = 1500;
    
    PRINT @sql;
    EXEC sp_executesql @sql, @paramsList, @BusinessEntityID
    GO
    

    最初は、コードは良さそうです。それでも、私たちの間のワシの目は @crlfに気付くでしょう。 変数。その価値は?変数は初期化されませんでした。つまり、NULLです。

    しかし、その変数のポイントは何ですか?後のセクションで、フォーマットとデバッグにおいてそれがどれほど重要であるかがわかります。とりあえず、目前のポイントに焦点を当てましょう。

    まず、NULL変数を動的SQL文字列に連結すると、結果はNULLになります。次に、PRINTは空白を印刷します。最後に、SP_EXECUTESQLはNULLの動的SQL文字列で正常に実行されます。しかし、何も返されません。

    NULLは、すでに悪い日に私たちを魅了する可能性があります。少し休憩してください。リラックス。その後、より明確な心で戻ってきます。

    パラメータ値のインライン化

    散らかっています。

    これが、動的SQL文字列への値のインライン化のようになります。文字列と日付には多くの一重引用符があります。注意しないと、オブライエンスとオニールズもエラーを引き起こします。また、動的SQLは文字列であるため、値を文字列に変換またはキャストする必要があります。これが例です。

    DECLARE @shipDate DATETIME = '06/11/2011';
     DECLARE @productID INT = 750;
     DECLARE @sql NVARCHAR(1000);
     SET @sql = N'SELECT
      soh.ShipDate
     ,sod.ProductID
     ,SUM(sod.OrderQty) AS TotalQty
     ,SUM(sod.LineTotal) AS LineTotal
     FROM Sales.SalesOrderHeader soh
     INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
     WHERE soh.ShipDate BETWEEN ' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + '''' + ' AND DATEADD(MONTH,1,' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + ''') ' +
     'AND sod.ProductID = ' + CAST(@productID AS VARCHAR(8)) +
     ' GROUP BY soh.ShipDate, sod.ProductID' +
     ' ORDER BY sod.ProductID';
     
     PRINT @sql;
     EXEC sp_executesql @sql;
    

    私はこれよりも厄介な動的文字列を見ました。一重引用符、CONVERT、およびCASTに注意してください。パラメータを使用すると、見栄えが良くなる可能性があります。以下でご覧いただけます

    DECLARE @shipDate DATETIME = '06/11/2011';
     DECLARE @productID INT = 750;
     DECLARE @sql NVARCHAR(1000);
     DECLARE @paramList NVARCHAR(500) = N'@shipDate DATETIME, @productID INT';
     SET @sql = N'SELECT
      soh.ShipDate
     ,sod.ProductID
     ,SUM(sod.OrderQty) AS TotalQty
     ,SUM(sod.LineTotal) AS LineTotal
     FROM Sales.SalesOrderHeader soh
     INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
     WHERE soh.ShipDate BETWEEN @shipDate AND DATEADD(MONTH,1,@shipDate)
     AND sod.ProductID = @productID
      GROUP BY soh.ShipDate, sod.ProductID
      ORDER BY sod.ProductID';
    
    PRINT @sql;
    EXEC sp_executesql @sql, @paramList, @shipDate, @productID
    GO
    

    見る?一重引用符が少なく、CONVERTとCASTがなく、クリーンでもあります。

    ただし、インライン値にはさらに危険な副作用があります。

    SQLインジェクション

    私たちがすべての人が良い世界に住んでいたとしたら、SQLインジェクションは考えられないでしょう。しかし、そうではありません。誰かが悪意のあるSQLコードをあなたのコードに挿入する可能性があります。どうすればこれが起こりますか?

    この例で使用するシナリオは次のとおりです。

    • 前の例のように、値は動的SQL文字列に融合されます。パラメータはありません。
    • 動的SQL文字列は、NVARCHAR(MAX)を使用して最大2GBです。悪意のあるコードを挿入するための多くのスペース。
    • 最悪の場合、動的SQL文字列は昇格された権限で実行されます。
    • SQLServerインスタンスはSQL認証を受け入れます。

    これは多すぎますか? 1人の男性が管理するシステムの場合、これが発生する可能性があります。とにかく誰も彼をチェックしません。大企業の場合、セキュリティに余裕のあるIT部門が存在することがあります。

    これは今日でも脅威ですか?クラウドサービスプロバイダーのAkamaiによると、インターネット/セキュリティレポートの状態です。 2017年11月から2019年3月の間に、SQLインジェクションはすべてのWebアプリケーション攻撃のほぼ3分の2を占めています。これは、調査されたすべての脅威の中で最も高いものです。非常に悪い。

    自分で見たいですか?

    SQLインジェクションの実践:悪い例

    この例では、SQLインジェクションを実行してみましょう。自分のAdventureWorksでこれを試すことができます データベース。ただし、SQL認証が許可されていることを確認し、昇格された権限で実行してください。

    DECLARE @lastName NVARCHAR(MAX) = 'Mu';
    DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
    DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);
    
    DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
    ' p.LastName ' + @crlf +
    ',p.FirstName ' + @crlf +
    ',a.AddressLine1 ' + @crlf +
    ',a.AddressLine2 ' + @crlf +
    ',a.City ' + @crlf +
    'FROM Person.Person p ' + @crlf +
    'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
    'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
    'WHERE p.LastName = ' + NCHAR(39) + @lastName + NCHAR(39) + ' ' + @crlf +
    'AND p.FirstName = '  + NCHAR(39) + @firstName + NCHAR(39);
    
    -- SELECT @sql;	-- uncomment if you want to see what's in @sql					
    EXEC sp_executesql @sql;
    GO
    

    上記のコードは、既存の会社の実際のコードを表すものではありません。アプリからも呼び出すことはできません。しかし、これは悪行を示しています。では、ここには何がありますか?

    まず、挿入されたコードは、 saのようなSQLアカウントを作成します。 、しかしそうではありません。そしてsa 、これには sysadminがあります 権限。最終的に、これは完全な特権でいつでもデータベースにアクセスするために使用されます。これが行われると、企業データの盗用、削除、改ざんなど、何でも可能になります。

    このコードは実行されますか?間違いなく!そして、そうなると、スーパーアカウントはサイレントに作成されます。そしてもちろん、ZhengMuの住所が結果セットに表示されます。他のすべては正常です。日陰だと思いませんか?

    上記のコードを実行した後、これも実行してみてください:

    SELECT IS_SRVROLEMEMBER('sysadmin','sà')

    1が返された場合、このは誰でも、彼は参加しています。 は。または、SQL ServerManagementStudioのSQLServerのセキュリティログインで確認することもできます。

    それで、あなたは何を得ましたか?

    怖いですよね? (これが本当ならそうです。)

    SQLインジェクションの実践:良い例

    それでは、パラメータを使用してコードを少し変更しましょう。他の条件は同じです。

    DECLARE @lastName NVARCHAR(MAX) = 'Mu';
    DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
    DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);
    
    DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
    ' p.LastName ' + @crlf +
    ',p.FirstName ' + @crlf +
    ',a.AddressLine1 ' + @crlf +
    ',a.AddressLine2 ' + @crlf +
    ',a.City ' + @crlf +
    'FROM Person.Person p ' + @crlf +
    'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
    'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
    'WHERE p.LastName = @lastName' + @crlf +
    'AND p.FirstName = @firstName';
    
    DECLARE @paramList NVARCHAR(300) = N'@lastName NVARCHAR(50), @firstName NVARCHAR(50)';
    
    -- SELECT @sql;	-- uncomment if you want to see what's in @sql
    EXEC sp_executesql @sql, @paramList, @lastName, @firstName;
    GO
    

    最初の例と比較して、結果には2つの違いがあります。

    • まず、ZhengMuの住所は表示されません。結果セットは空白です。
    • その後、反逆者のアカウントは作成されません。 IS_SRVROLEMEMBERを使用すると、NULLが返されます。

    どうしたの?

    パラメータが使用されるため、 @firstNameの値 「鄭」です。 CREATELOGINsàWITHPASSWORD=” 12345”; ALT’ 。これはリテラル値と見なされ、50文字のみに切り捨てられます。上記のコードの名パラメータを確認してください。 NVARCHAR(50)です。そのため、結果セットは空白になります。そのような名の人はデータベースにありません。

    これはSQLインジェクションの一例であり、それを回避する1つの方法です。実際のことをすることにもっと関わっています。しかし、動的SQLのインライン値がなぜ悪いのかを明確にしたいと思います。

    パラメータスニッフィング

    アプリからの実行速度の遅いストアドプロシージャを経験したことがありますが、SSMSで実行しようとすると高速になりましたか?アプリで使用されている正確なパラメータ値を使用したため、困惑しています。

    それが実際のパラメータスニッフィングです。 SQL Serverは、ストアドプロシージャが最初に実行または再コンパイルされるときに、実行プランを作成します。次に、次の実行のために計画を再利用します。 SQL Serverは毎回プランを再作成する必要がないため、これはすばらしいことです。ただし、パラメータ値が異なれば、高速で実行するために別の計画が必要になる場合があります。

    これは、SP_EXECUTESQLとプレーンな静的SQLを使用したデモです。

    DECLARE @sql NVARCHAR(150) = N'SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID';
    DECLARE @paramList NVARCHAR(100) = N'@ProductSubcategoryID INT';
    DECLARE @ProductSubcategoryID INT = 23;
    
    EXEC sp_executesql @sql, @paramList, @ProductSubcategoryID
    

    これはとても簡単です。図3の実行計画を確認してください。

    次に、静的SQLを使用して同じクエリを試してみましょう。

    DECLARE @ProductSubcategoryID INT = 23;
    SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID
    

    図4を確認してから、図3と比較してください。

    図3では、インデックスシーク およびネストされたループ 使用されています。ただし、図4では、クラスター化インデックススキャンです。 。現時点では、認識できるパフォーマンスの低下はありませんが、これは、パラメータのスニッフィングが単なる想像ではないことを示しています。

    クエリが遅くなると、これは非常にイライラする可能性があります。それを回避するために、再コンパイルやクエリヒントの使用などの手法を使用することになる可能性があります。これらのいずれにも欠点があります。

    SP_EXECUTESQLのフォーマットされていない動的SQL文字列

    このコードで何がうまくいかない可能性がありますか?

    DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) AS ProductCount' +
                                  'FROM Production.Product';
    PRINT @sql;
    EXEC sp_executesql @sql;
    

    短くてシンプルです。ただし、下の図5を確認してください。

    動的SQL文字列を作成するときに、キーワードとオブジェクトの間に1つのスペースを入れてもかまわない場合、エラーが発生します。図5のように、 ProductCount 列エイリアスとキーワードFROMの間にスペースはありません。文字列の一部が次のコード行に流れると、混乱を招きます。構文が正しいと思わせます。

    文字列はコードウィンドウで2行を使用しましたが、PRINTの出力は1行を示していることにも注意してください。これが非常に長いコマンド文字列であると想像してみてください。 [メッセージ]タブから文字列を適切にフォーマットするまで、問題を見つけるのは困難です。

    この問題を解決するには、キャリッジリターンとラインフィードを追加します。おそらく変数@crlfに気付くでしょう 前の例から。動的SQL文字列をスペースと改行でフォーマットすると、動的SQL文字列が読みやすくなります。これはデバッグにも最適です。

    JOINを使用したSELECTステートメントについて考えてみます。以下の例のように、数行のコードが必要です。

    DECLARE @sql NVARCHAR(400)
    DECLARE @shipDate DATETIME = '06/11/2011';
    DECLARE @paramList NVARCHAR(100) = N'@shipDate DATETIME';
    DECLARE @crlf NCHAR(2) = NCHAR(13) + NCHAR(10);
    
    set @sql = N'SELECT ' + @crlf +
     'soh.ShipDate ' + @crlf +
     ',sod.ProductID ' + @crlf +
     ',SUM(sod.OrderQty) AS TotalQty ' + @crlf +
     ',SUM(sod.LineTotal) AS LineTotal ' + @crlf +
     'FROM Sales.SalesOrderHeader soh ' + @crlf +
     'INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID ' + @crlf +
     'WHERE soh.ShipDate = @shipDate' + @crlf +
     'GROUP BY soh.ShipDate, sod.ProductID ' + @crlf +
     'ORDER BY sod.ProductID';
    
     PRINT @sql;
     EXEC sp_executesql @sql,@paramList,@shipDate
     GO
    

    文字列をフォーマットするには、 @crlf 変数は、キャリッジリターンのNCHAR(13)とラインフィードのNCHAR(10)に設定されます。 SELECTステートメントの長い文字列を分割するために、各行に連結されます。 [メッセージ]タブで結果を確認するには、[印刷]を使用します。下の図6の出力を確認してください。

    動的SQL文字列をどのように形成するかはあなた次第です。明確で読みやすく、時が来たときにデバッグしやすいものにするために、あなたに合ったものは何でも。

    サニタイズされていないオブジェクト名

    何らかの理由でテーブル、ビュー、またはデータベースの名前を動的に設定する必要がありますか?次に、エラーを回避するために、これらのオブジェクト名を「サニタイズ」または検証する必要があります。

    この例では、テーブル名を使用しますが、検証の原則はビューにも適用される場合があります。次にどう対処するかは異なります。

    以前は、PARSENAMEを使用してスキーマ名をテーブル名から分離していました。文字列にサーバー名とデータベース名がある場合にも使用できます。ただし、この例では、スキーマ名とテーブル名のみを使用します。残りはあなたの素晴らしい心に任せます。これは、テーブルにスペースの有無に関係なく機能します。テーブル名またはビュー名のスペースは有効です。したがって、 dbo.MyFoodCravingsで機能します または[dbo]。[MyFoodCravings]

    テーブルを作成しましょう。

    CREATE TABLE [dbo].[My Favorite Bikes]
    (
    	id INT NOT NULL,
    	BikeName VARCHAR(50)
    )
    GO
    

    次に、SP_EXECUTESQLを使用するスクリプトを作成します。これにより、1列のキーが指定された任意のテーブルに対して汎用のDELETEステートメントが実行されます。最初に行うことは、完全なオブジェクト名を解析することです。

    DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
    DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
    DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);
    

    このようにして、スキーマをテーブルから分離します。さらに検証するために、OBJECT_IDを使用します。 NULLでない場合は、有効です。

    IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
    BEGIN
    	PRINT @object + ' is valid!'
    	-- do the rest of your stuff here
    END
    ELSE
    BEGIN
            PRINT 'Invalid object name ' + @object
    	-- if you need to do anything else, insert it here
    END
    

    QUOTENAMEを使用したことにも注意してください。これにより、スペースを含むテーブル名が角かっこで囲まれてエラーが発生しないようになります。

    しかし、キー列を検証するのはどうですか? sys.columnsでターゲットテーブルの列の存在を確認できます 。

    IF (SELECT COUNT(*) FROM sys.columns
    	    WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
    		  AND [name] = @idKey) > 0
    BEGIN
         -- add miscellaneous code here, if needed
         EXEC sp_executesql @sql, @paramsList, @id
    END
    ELSE
    BEGIN
         PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
         -- if you need to do anything else, insert it here
    END
    

    これが、私たちが達成したいことの完全なスクリプトです。

    DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
    DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
    DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);
    DECLARE @isDebug BIT = 1;
    DECLARE @idKey NVARCHAR(128) = N'id';
    
    DECLARE @sql NVARCHAR(200) = N'DELETE FROM @object WHERE @idKey = @id';
    DECLARE @id INT = 0;
    DECLARE @paramList NVARCHAR(100) = N'@id INT';
    
    IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
    BEGIN
       PRINT @object + ' is valid!'
       
       IF (SELECT COUNT(*) FROM sys.columns
           WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
             AND [name] = @idKey) > 0
       BEGIN
           SET @sql = REPLACE(@sql, '@object', QUOTENAME(@schemaName) + '.' +          
                      QUOTENAME(@tableName));
           SET @sql = REPLACE(@sql, '@idkey', QUOTENAME(@idKey));
           IF @isDebug = 1
    	   PRINT @sql;
           EXEC sp_executesql @sql, @paramList, @id
       END
       ELSE
           PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
    END
    ELSE
    BEGIN
       PRINT 'Invalid object name ' + @object
       -- if you need to do anything else, insert it here
    END
    GO
    

    このスクリプトの結果は、以下の図7にあります。

    他のテーブルでこれを試すことができます。 @objectを変更するだけです 、 @idkey 、および @id 変数値。

    デバッグの準備なし

    エラーが発生する可能性があります。したがって、根本的な原因を見つけるには、生成された動的SQL文字列を知る必要があります。私たちは、動的SQL文字列の形式を推測する占い師や魔術師ではありません。したがって、デバッグフラグが必要です。

    前の図7で、動的SQL文字列がSSMSの[メッセージ]タブに出力されていることに注意してください。 @isDebugを追加しました BIT変数を作成し、コードで1に設定します。値が1の場合、動的SQL文字列が出力されます。これは、このようなスクリプトまたはストアドプロシージャをデバッグする必要がある場合に適しています。デバッグが完了したら、ゼロに戻すだけです。これがストアドプロシージャの場合、このフラグをデフォルト値がゼロのオプションのパラメータにします。

    動的SQL文字列を表示するには、2つの可能な方法を使用できます。

    • 文字列が8000文字以下の場合は、PRINTを使用します。
    • または、文字列が8000文字を超える場合はSELECTを使用します。

    SP_EXECUTESQLで使用される動的SQLのパフォーマンスが低い

    SP_EXECUTESQLに実行速度の遅いクエリを割り当てると、SP_EXECUTESQLが遅くなる可能性があります。限目。これには、パラメータスニッフィングの問題はまだ含まれていません。

    したがって、動的に実行するコードから静的に開始します。次に、実行プランとSTATISTICSIOを確認します。作成する必要のある欠落しているインデックスがあるかどうかを確認します。早めに調整してください。

    SP_EXECUTESQLの要点

    SP_EXECUTESQLを使用することは、強力な武器を使用するようなものです。しかし、それを振るう人はそれに熟練している必要があります。これもロケット科学ではありませんが。あなたが今日の初心者であれば、練習に合わせて常識になる可能性があります。

    この落とし穴のリストは完全ではありません。しかし、それは一般的なものをカバーしています。さらに詳しい情報が必要な場合は、次のリンクを確認してください:

    • ErlandSommarskogによる動的SQLの呪いと祝福
    • SP_EXECUTESQL(Transact-SQL)、Microsoftによる

    このような?次に、お気に入りのソーシャルメディアプラットフォームで共有してください。コメントセクションで、定評のあるヒントを共有することもできます。


    1. C#プログラム内でストアドプロシージャを実行する方法

    2. Oracleでテーブルを変更する方法

    3. Oracleはnullと空の文字列を区別していませんか?

    4. SQLite外部キー