想像できるようにT-SQLで変数を使用できないのはなぜですか?


18

許してください、私はSQLの世界に移行した開発者です。変数を追加することでSQLを改善できると思いましたが、期待どおりに機能しませんでした。誰かがこれがうまくいかない理由を教えてもらえますか?私は回避したくない、これがうまくいかない理由を知りたいのですが、正当な理由があると確信しているので、私はそれがうまくいくと思いますが、現在それは飛び出していません。

DECLARE @DatabaseName varchar(150)
SET @DatabaseName = 'MyAmazingDatabaseName'

CREATE DATABASE @DatabaseName
GO

USE @DatabaseName
GO

そのために動的SQLを使用する必要があります。mssqltips.com/sqlservertip/1160/...
Stijn Wynants

3
コメントしてくれてありがとう、しかし私の質問によると、これは私が探している答えではありません。私が示したようにできない理由を誰かが知っているかどうか知りたいです。
ガレス16

2
USEコマンドをパラメーター付きで使用することはできないためです。
TT。

2
既に与えられているが、それ自体の答えには不十分な答えに加えて、変数は現在のバッチの最大にスコープされます。(変数のスコープをSQL Serverのスコープよりも狭くすることができるかどうかはわからないのです。)を取得するGOと、以前に宣言された変数が消えます。SQLCMD変数を調べることもできますが、これはシナリオに適用される場合と適用されない場合があります。
CVn

1
データベース名と列名は文字列値と考える傾向がありますが、SQLのコンテキストでは識別子です。あなたがしようとしていることは、x = 7を期待するのと同じです。'x' = 7と同じである; 他の言語で。x = 7と同じ「x」= 7を扱うコンピューター言語を作成できるように、Create Table XをCreate Table「X」と同じように扱うRDBMSを作成できます。しかし、それはSQLではありません。
user1008646 16

回答:


20

Books Onlineの変数ページ

変数は式でのみ使用でき、オブジェクト名やキーワードの代わりには使用できません。動的SQLステートメントを作成するには、EXECUTEを使用します。

たとえば、where句で変数を使用した場合、期待どおりに機能します。なぜかというと、変数を評価して存在を確認できないパーサーと関係があると思います。実行時、クエリは最初に構文とオブジェクトについて解析され、解析が成功した場合、変数が設定されるポイントでクエリが実行されます。

DECLARE @name varchar(20);
SET @name = 'test';

CREATE TABLE [#tmp]([val] varchar(10));

insert into #tmp
values('test')

SELECT *
FROM [#tmp]
WHERE [val] = @name;

3
可能な限り、動的SQLは避けてください。evalJavaScriptやPythonなどの手続き型言語で関数を使用するのに似たSQL です。これは、セキュリティホールをすばやく作成する方法です。
jpmc26 16

1
@ jpmc26:動的SQLを使用しない、より安全な方法は何ですか?
ロバートハーベイ

1
@RobertHarvey回避するのが最善だからといって、まったく同じ機能を備えた代替手段が常にあるというわけではありません。;)多くの場合、答えの一部は、「問題に対して完全に異なるソリューションを使用する」です。時にはそれが最善のことかもしれませんが、十分な量の審議と代替手段を怠っていないことを確認することなしではなく、それでも健康的な注意が必要です。
jpmc26 16

2
@ jpmc26:OPの例は、「Code-First」ORMがデータベースにテーブルを設定するために行うことのように見えます。動的SQLは原則として安全ではありませんが、エンドユーザーはその特定のコードに触れることはありません。
ロバートハーベイ

@RobertHarvey「エンドユーザー」とみなす人によって異なります。DBを展開するスクリプトの場合、開発者と場合によってはシステム管理者を「エンドユーザー」と見なします。事故を避ける以外の理由がない限り、その場合でも安全でない入力を拒否するようにシステムを設計します。また、...ので、OPは、このコードに触れている「触れたことがない」ためとして
jpmc26

17

SQLステートメントでの変数の使用に関する制限は、SQLのアーキテクチャから生じます。

SQLステートメントの処理には3つのフェーズがあります。

  1. 準備-ステートメントが解析され、実行プランがコンパイルされ、アクセスするデータベースオブジェクト、アクセス方法、および関連方法が指定されます。実行計画は計画キャッシュに保存されます。
  2. バインディング-ステートメント内の変数はすべて実際の値に置き換えられます。
  3. 実行-キャッシュされたプランはバインドされた値で実行されます。

SQLサーバーは、プログラマーから準備手順を隠し、OracleやDB2などの従来のデータベースよりもはるかに高速に実行します。パフォーマンス上の理由から、SQLは最適な実行プランの決定に多くの時間を費やす可能性がありますが、再起動後に初めてステートメントが検出されたときにのみ実行します。

そのため、静的SQLでは、変数は実行計画を無効にしない場所でのみ使用でき、テーブル名、列名(WHERE条件の列名を含む)などには使用できません。

動的SQLは、制限を回避できない場合のために存在し、プログラマーは実行に少し時間がかかることを知っています。動的SQLは悪意のあるコードインジェクションに対して脆弱である可能性があるため、注意してください!


7

ご覧のように、「理由」の質問には、言語の歴史的根拠や根底にある仮定など、別の種類の回答が必要ですが、本当にその正義を実行できるかはわかりません。

SQL MVP Erland Sommarskogによるこの包括的な記事は、メカニズムとともにいくつかの理論的根拠を提供しようとしています。

動的SQLの呪いと祝福

クエリプランのキャッシュ

SQL Serverで実行するすべてのクエリには、クエリプランが必要です。初めてクエリを実行すると、SQL Serverはクエリのクエリプランを作成します-または用語の説明に従って-クエリをコンパイルします。SQL Serverはプランをキャッシュに保存し、次にクエリを実行するときにプランが再利用されます。

これ(およびセキュリティ、以下を参照)がおそらく最大の理由です。

SQLは、クエリは1回限りの操作ではなく、繰り返し使用されるという前提の下で動作します。テーブル(またはデータベース!)がクエリで実際に指定されていない場合、将来の使用のために実行プランを生成および保存する方法はありません。

はい、実行したすべてのクエリが再利用されるわけではありませんが、これはSQLのデフォルトの動作前提であるため、「例外」は例外的であることを意味します。

Erlandがリストした他のいくつかの理由(彼はストアドプロシージャを使用する利点を明示的にリストしていますが、これらの多くはパラメータ化された(動的でない)クエリの利点でもあることに注意してください):

  • 許可システム:SQLエンジンは、操作対象のテーブル(またはデータベース)がわからない場合、クエリを実行する権限があるかどうかを予測できません。動的SQLを使用した「パーミッションチェーン」は、苦痛です。
  • ネットワークトラフィックの削減:ストアドプロシージャの名前といくつかのパラメーター値をネットワーク経由で渡すことは、長いクエリステートメントよりも短くなります。
  • ロジックのカプセル化:他のプログラミング環境のロジックをカプセル化することの利点に精通している必要があります。
  • 使用されているものを追跡する:列定義を変更する必要がある場合、それを呼び出すすべてのコードを見つけるにはどうすればよいですか?コードがストアドプロシージャにある場合のみ、SQLデータベース内の依存関係を見つけるためのシステムプロシージャが存在します。
  • SQLコードの記述のしやすさ:ストアドプロシージャを作成または変更するときに構文チェックが行われるため、エラーが少なくなることが期待されます。
  • バグと問題への対処:DBAは、刻々と変化する動的SQLよりもはるかに簡単に、個々のストアドプロシージャのパフォーマンスをトレースおよび測定できます。

繰り返しますが、これらのそれぞれには、ここでは取り上げない100のニュアンスがあります。


2

動的SQLを使用する必要があります

DECLARE @DatabaseName varchar(150) = 'dbamaint'
declare @sqltext nvarchar(max) = N''

set @sqltext = N'CREATE DATABASE '+quotename(@DatabaseName)+ ';'

print @sqltext 

-- once you are happy .. uncomment below
--exec sp_executesql @sqltext
set @sqltext = ''
set @sqltext = N'use '+quotename(@DatabaseName)+ ';'
print @sqltext 
-- once you are happy .. uncomment below
--exec sp_executesql @sqltext

以下はprintの出力です。コメントを外すとexec sp_executesql @sqltext、ステートメントが実際に実行されます...

CREATE DATABASE [dbamaint];
use [dbamaint];

1
はい、私はそれを知っていますが、変数を使用できない理由を誰かが知っているかどうか知りたいですか?
ガレス16

T-SQLパーサーは構文エラーをスローします。パーサーが認識する有効なT-SQLではありません。
キンシャー

Kinに感謝します。それには十分な理由があるはずです。データベース名に「@」が含まれている可能性があり、おそらく他のより複雑な理由が考えられます。
ガレス16

1
はい、@を含めることができ、それが主な理由だと思います。msdn.microsoft.com/en-us/library/ms175874.aspx
パヴェルTajs

1
@gazeranco私を信じてください。SQLServerで作業する人は誰でも、より多くのコマンドが定数識別子の代わりに変数を受け入れることを望んでいます。
db2
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.