web-dev-qa-db-ja.com

単純なSQL CTE更新

私はこのCTE更新stmtで少し困惑しています:

DECLARE @a TABLE (ID int, Value int);
DECLARE @b TABLE (ID int, Value int);
INSERT @a VALUES (1, 10), (2, 20);
INSERT @b VALUES (1, 100),(2, 200);

WITH cte AS 
(
    SELECT * FROM @a
)
UPDATE cte
SET    Value = b.Value
FROM   cte AS a
INNER JOIN @b AS b 
ON     b.ID = a.ID

SELECT * FROM @a
GO

これにより、テーブル@aの行が両方とも100になるのはなぜですか? ID 1の場合は100、ID 2の場合は200にする必要があると思いました。

更新を行うために共通のテーブル式の代わりにテーブルを使用すると、期待どおりの結果が得られます。

DECLARE @a TABLE (ID int, Value int);
DECLARE @b TABLE (ID int, Value int);
INSERT @a VALUES (1, 10), (2, 20);
INSERT @b VALUES (1, 100),(2, 200);

SELECT  *
FROM    @a

UPDATE @a
SET Value = b.Value
FROM @a AS XX
INNER JOIN @b AS b ON b.ID = xx.ID

SELECT  *
FROM    @a

この結果、テーブル@aは100と200になります。しかし、両方の値が同じになるはずではありませんか?参照問題の以前の説明に基づいて? -参照されているXXではなく、@aテーブルに対して更新が行われています。

7
user1967701

MguerraTorres 'answer を展開するには:

(2次クエリの情報で更新されます)

最初のクエリでは、UPDATE cteはCTEからテーブルを更新するように言っています。

FROM cte as aは、CTEのテーブルをaとして参照するように言っています。

したがって、2つの場所でCTEを参照しました。

気付かないかもしれませんが、参照をサブクエリに置き換えた場合と同じように、クエリに表示されるたびにCTEが再評価されます。 CTEを2回別々に参照したので、DBエンジンが動作するための2つの個別の結果セットが生成されました。

b.Value where a.ID = b.IDを使用するとすると、2つの行があります。1つはb.Valueが100、もう1つは200です-テーブルbとfrom 2番目のCTE結果セット。

ただし、これらの2つの行に基づいてfirst CTE結果セットを更新しています。したがって、返される2つの行の最初の結果セットでeach rowを更新します。 2つの結果セットの間にno relationshipがありますが、それらは同じ基礎となるデータを表しています。エンジンは、更新を実行するために、結合の結果と最初の結果セットの間でCROSS JOINを実行しています。

UPDATEステートメントは、両方の行を200に更新し、次に100に更新します(エンジンが交差結合された行を適用する最も速い方法を決定するため、入力された順序で行かない場合があります)。同じ複数の行から更新されているため、両方の行が同じ値に更新されます。

最初のクエリは、機能的には次のものと同じです。

DECLARE @a TABLE (ID int, Value int);
DECLARE @b TABLE (ID int, Value int);
INSERT @a VALUES (1, 10), (2, 20);
INSERT @b VALUES (1, 100),(2, 200);


WITH cte AS 
(
    SELECT * FROM @a
)
UPDATE cte
SET    Value = b.Value
FROM   (SELECT * FROM @a) AS a
INNER JOIN @b AS b 
ON     b.ID = a.ID

SELECT * FROM @a
GO

2番目のクエリでは、DBエンジンはa@aの両方がクエリ外のテーブルを参照し、a@aは同じものを意味することを認識していますなので、更新を実行するときに、@bから@aまでの行を正しく結び付けます。


コメントで、あなたは尋ねました:

結果は常に両方とも100になりますか?または、両方とも200になることもあります-ご覧のとおり、ここには明確なルールはありませんか?

100か200かは異なります。

最初のクエリに表示されている同じステートメントを同じ方法で実行すると、ほぼ確実に同じ結果が得られると思います。

ただし、現実の世界では、テーブルに他のアクティビティが表示されているため、特に時間の経過とともに、どちらか一方の結果を実際に処理することはできませんでした。それは、DBエンジンが結合内のテーブルをどのように一致させ、更新を適用する際に行を処理したかに依存します。

10
RDFozz

「a」でcteをエイリアスする際の単純な間違い

「cte」を更新する代わりに「a」を更新する必要があります

DECLARE @a TABLE (ID int, Value int);
DECLARE @b TABLE (ID int, Value int);
INSERT @a VALUES (1, 10), (2, 20);
INSERT @b VALUES (1, 100),(2, 200);


WITH cte AS (SELECT ID, Value 
         FROM @a)

  UPDATE a --Changed from "UPDATE cte"
  SET Value = b.Value
  FROM cte AS a
  INNER JOIN @b AS b ON b.ID = a.ID;



  SELECT * FROM @a;


ID          Value
----------- -----------
1           100
2           200

(2 rows affected)
9
MguerraTorres