私はSQLServer 2008R2を使用しています。私は次のSQLを実行しています
use tempdb
go
create table t1 ( c1 int, c2 int, c3 uniqueidentifier null)
go
insert t1(c1,c2) values (1,2),(1,3)
go
update t1
set c2 = 5 + c2,
c3 = t2.c3
from t1 join ( select c1=1, c3=newid()) as t2 on t1.c1 = t2.c1
go
select * from t1
go
drop table t1
go
クエリの結果は次のとおりです。
c1 c2 c3
----------- ----------- ------------------------------------
1 7 D6BE2119-CECB-4413-94E4-6099E5CC9028
1 8 0BF8A3E2-4E66-4091-A1B0-4FB8E130B347
C3のGUID値は同じであると期待していました。派生テーブルt2は、相互に関連するサブではありませんが、テーブルt1の各行に対して「再評価」されているようです。 -クエリ。これは予期される動作ですか?
派生テーブル_t2
_については、NEWID()
が1回計算されることが期待されます。 T1とT2の結合結果セットの各行に対して計算されるため、これは当てはまりません。
簡単に言えば、各行に対して_where T1.C1 = T2.C1
_、つまり_where T1.C1=1
_です。
これに関するMicrosoftの声明の一部は次のとおりです。
ループを閉じる。 。 。この質問については、開発チームと話し合いました。そして最終的には、次の理由により、現在の動作を変更しないことを決定しました。
1)オプティマイザは、スカラー関数の実行のタイミングや回数を保証しません。これは老舗の信条です。これは、オプティマイザがクエリプランの実行を大幅に改善するのに十分な自由を可能にする基本的な「余裕」です。
そのため、オプティマイザーは実行のタイミングまたは数を選択して、最高のパフォーマンスを得ることができます。
見積もりの出典、その他の情報と例、およびMicrosoftからの回答
それを説明するために、UPDATE
ステートメント用に作成されたクエリプランを確認するのが最善です。
バットの右側には、結合述語は表示されませんが、単にテーブルスキャンが表示されます。そのテーブルスキャンに述語が追加され、行のみを取得します_where C1 = 1
_
これは、 'join
'を述語に還元したものです。
この後、この述部に一致する2つの行が見つかり、最初の_Compute Scalar
_演算子によって処理されます。
そして、NEWID()
は、結合されたテーブル+派生テーブルの各列に対して計算されます。
回避策
明らかな修正は、変数を使用してNEWID()
値を保持し、結合して再利用する前に計算することです。
_create table t1 ( c1 int, c2 int, c3 uniqueidentifier null)
go
insert t1(c1,c2) values (1,2),(1,3)
go
DECLARE @test uniqueidentifier = newid()
update t1
set c2 = 5 + c2,
c3 = t2.c3
from t1 join
( select c1=1, c3=@test) as t2 on t1.c1 = t2.c1
go
select * from t1
go
drop table t1
go
_
複数のソース列(T2)がある場合は、NEWID()
値を一時テーブルまたはテーブル変数に格納する必要があります
もう1つの興味深い部分:
したがって、選択肢があります。たとえば、JOINの結果がネストされたループの実行のセマンティクスに従うように、非決定論的(副作用)コードの存在下で特定の動作を保証したい場合は、 UCが指摘しているように、適切なオプションを使用してその動作を強制できます。ただし、結果のコードの実行速度は遅くなります。これは、事実上、クエリオプティマイザーをホブリングするコストです。
関数を使用することで、結合の前に、オプティマイザーにソーステーブルのNEWID()を最初に計算させることができます。しかし、私は手元のクエリでそれを行うことができません(おそらくクエリのせいではありません)。