Einfaches SQL CTE-Update

7

Ich bin ein wenig ratlos über dieses CTE-Update:

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

Warum führt dies dazu, dass die Tabelle @afür beide Zeilen 100 enthält? Ich dachte, es sollte 100 für ID 1 und 200 für ID 2 sein.

Ich erhalte erwartete Ergebnisse, wenn ich für die Aktualisierungen eine Tabelle anstelle eines allgemeinen Tabellenausdrucks verwende:

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

Dies ergibt eine Tabelle @amit 100 und 200. Aber sollen wir nicht beide die gleichen Werte erhalten? basierend auf der vorherigen Erklärung des Referenzierungsproblems? - Aktualisierung der @aTabelle und nicht des referenzierten XX.

user1967701
quelle

Antworten:

10

Um die Antwort von MguerraTorres zu erweitern :

(Aktualisiert mit den Informationen aus Ihrer sekundären Abfrage)

In Ihrer ersten Abfrage UPDATE cteheißt es, die Tabelle vom CTE zu aktualisieren.

FROM cte as asagt, auf die Tabelle aus dem CTE als zu verweisen a.

Wir haben uns also an zwei Stellen auf unseren CTE bezogen.

Was Sie möglicherweise nicht bemerken, ist, dass ein CTE jedes Mal neu bewertet wird, wenn er in Ihrer Abfrage angezeigt wird, als hätten Sie die Referenz durch eine Unterabfrage ersetzt. Da Sie zweimal auf den CTE verwiesen haben, haben Sie zwei separate Ergebnismengen generiert , mit denen die DB-Engine arbeiten kann.

Wenn Sie sagen, b.Valuewo zu verwenden a.ID = b.ID, haben wir zwei Zeilen - eine mit b.Value100 und eine mit 200 - aus der Tabelle bund aus unserer zweiten CTE-Ergebnismenge.

Wir aktualisieren jedoch die erste CTE-Ergebnismenge basierend auf diesen beiden Zeilen. Daher wird jede Zeile in dieser ersten Ergebnismenge aus den beiden zurückgegebenen Zeilen aktualisiert . Es gibt keine Beziehung zwischen den beiden Ergebnismengen, obwohl sie dieselben zugrunde liegenden Daten darstellen. Die Engine führt CROSS JOINzwischen den Ergebnissen Ihres Joins und der ersten Ergebnismenge einen Vorgang durch, um die Aktualisierung durchzuführen.

Ihre UPDATEAnweisung aktualisiert beide Zeilen auf 200 und dann auf 100 (da die Engine entscheidet, wie die gekreuzten Zeilen am schnellsten angewendet werden sollen, werden sie möglicherweise nicht in der Reihenfolge angezeigt, in der sie eingegeben wurden). Beide Zeilen werden auf denselben Wert aktualisiert, da sie aus denselben mehreren Zeilen aktualisiert werden.

Ihre erste Abfrage ist funktional identisch mit:

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

In Ihrer zweiten Abfrage, weiß die DB - Engine , dass beide aund @aeine Tabelle außerhalb der Abfrage verweisen, und sie weiß , dass aund @adas gleiche bedeuten, so ist es richtig , die Reihen von bindet @bzu , @awenn das Update durchführen.


In den Kommentaren haben Sie gefragt:

Wäre das Ergebnis für beide immer 100? oder kann es manchmal 200 für beide sein - Wie ich sehe, gibt es hier keine klare Regel?

Ob es 100 oder 200 ist, kann variieren.

Ich würde sagen, dass es wahrscheinlich ist, dass Sie mit den gleichen Aussagen, die in Ihrer ersten Abfrage gezeigt wurden und auf die gleiche Weise ausgeführt wurden, mit ziemlicher Sicherheit das gleiche Ergebnis erzielen würden.

In der realen Welt, in der Tabellen andere Aktivitäten sehen, konnte man jedoch nicht wirklich das eine oder andere Ergebnis erzielen, insbesondere im Laufe der Zeit. Dies hängt davon ab, wie die DB-Engine mit den Tabellen im Join übereinstimmt und dann die Zeilen beim Anwenden des Updates verarbeitet.

RDFozz
quelle
1
Siehe diesen alten Blog-Beitrag von Hugo Kornelis: Lasst uns UPDATE FROM ablehnen!
Ypercubeᵀᴹ
9

Einfacher Fehler beim Aliasing von cte mit "a"

Sie sollten "a" aktualisieren, anstatt "cte" zu aktualisieren.

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)
MguerraTorres
quelle
Danke, das ist Helpgul. Aber können Sie bitte erklären, warum das Original beide 100 für Tabelle @a produziert?
user1967701
Magie? Nein, im Ernst, weil Sie den Parser durch die Verwendung von UPDATE FROM verwirren und das Aliasing nicht korrekt ausführen. Sie könnten "SELECT cte. * From cte AS a" nicht ausführen, da Sie Ihre Tabelle mit a aliasen. Dies ist also nicht sehr unterschiedlich.
MguerraTorres