sql-server – 简单的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.但是我们不应该得到两个相同的值吗?基于先前对引用问题的解释? – 更新@a表而不是引用的XX.

最佳答案
要在MguerraTorres’ answer上扩展:

(更新了辅助查询中的信息)

在您的第一个查询中,UPDATE cte表示要从CTE更新表.

来自cte的说法是指CTE中的表格.

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

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

当你说使用b.Value,其中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

在第二个查询中,数据库引擎知道a和@a都引用查询外的表,并且它知道a和@a表示相同的东西,所以它在执行时正确地将@b的行绑定到@a更新.

在评论中,你问:

Would the result be always be 100 for both? or can it be 200 for both sometimes — As I see there is no clear rule here?

无论是100还是200都可以变化.

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

然而,在现实世界中,通过表格查看其他活动,您无法真正处理某个结果或其他结果,尤其是随着时间的推移.这取决于数据库引擎如何匹配连接中的表,然后在应用更新时处理行.

转载注明原文:sql-server – 简单的SQL CTE更新 - 代码日志