简单的 SQL CTE 更新

use*_*701 7 sql-server cte update

我对这个 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
Run Code Online (Sandbox Code Playgroud)

为什么这会导致表@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
Run Code Online (Sandbox Code Playgroud)

这导致表中@a包含 100 和 200。但是我们不应该得到相同的值吗?基于之前对引用问题的解释?-- 对@a表进行更新,而不是对引用的 XX。

RDF*_*ozz 10

扩展MguerraTorres 的 回答

(使用您的辅助查询中的信息更新)

在您的第一个查询中,UPDATE cte说从 CTE 更新表。

FROM cte as a说将 CTE 中的表称为a.

所以,我们在两个地方提到了我们的 CTE。

您可能没有意识到,每次 CTE 出现在您的查询中时都会重新评估它,就像您用子查询替换了引用一样。由于您已两次单独引用 CTE,因此您已为数据库引擎生成了两个单独的结果集以供使用。

当您说使用b.Valuewhere 时a.ID = b.ID,我们有两行 - 一行b.Value是 100,另一行是 200 - 来自表b和我们的第二个 CTE 结果集。

但是,我们正在根据这两行更新第一个CTE 结果集。因此,它会根据返回的两行更新第一个结果集中的每一行。有没有关系的两个结果之间,即使它们表示相同的底层数据。引擎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
Run Code Online (Sandbox Code Playgroud)

在你的第二个查询,数据库引擎知道这两个a@a引用查询外的表,它知道a@a意思是相同的,所以它正确地从捆绑的行@b@a执行更新时。


在评论中,你问:

两者的结果总是 100 吗?或者有时两者都是 200 - 正如我所见,这里没有明确的规则?

是 100 还是 200 可能会有所不同。

我想说的是,考虑到您的第一个查询中显示的相同语句,以相同的方式执行,您几乎肯定会得到相同的结果。

然而,在现实世界中,由于表格可以看到其他活动,您无法真正确定一种结果或另一种结果,尤其是随着时间的推移。这将取决于数据库引擎如何匹配连接中的表,然后在应用更新时处理行。


SQL*_*DBA 9

用“a”别名 cte 的简单错误

您应该更新“a”而不是更新“cte”

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)
Run Code Online (Sandbox Code Playgroud)