sql server如何在有多个选项的update语句中选择值?

时间:2009-09-02 22:24:15

标签: sql-server

我在SQL Server中有一个更新语句,其中有四个可以根据连接分配的值。似乎SQL有一个算法可以选择一个值而不是另一个值,我不确定该算法是如何工作的。

作为一个例子,假设有一个名为Source的表,其中包含两列(匹配和数据)结构如下: (匹配列仅包含1,数据列每行增加1)
匹配数据
`--------------------------
 1 1
 1 2
 1 3
 1 4

该表将更新另一个名为Destination的表,其中两列的结构如下:
匹配数据
`--------------------------
 1 NULL

如果您想以下列方式更新目的地中的ID字段:

  

UPDATE
    目的地
   SET
     Data = Source.Data   从
  目的地
  INNER JOIN
  来源
  ON
  Destination.Match = Source.Match

运行此查询后,将有四个可能的选项将Destination.ID设置为。我发现搞乱Source的索引会对Destination的设置产生影响,并且看起来SQL Server只是使用它找到匹配的第一个值来更新Destination表。

这是准确的吗? SQL Server是否可能按顺序更新每个可能值的Destination,并且最终得到的结果与使用它找到的第一个值进行更新时的结果相同?似乎可能有问题的是,它似乎会随机选择一行进行更新,而不是在出现这种情况时抛出错误。

谢谢。

P.S。我为糟糕的格式道歉。希望,意图很明确。

2 个答案:

答案 0 :(得分:10)

所有结果设置为Data。在查询之后你最终得到的是取决于返回结果的顺序(它最后设置的顺序)。

由于没有ORDER BY子句,因此您可以使用Sql Server提供的任何顺序。这通常遵循磁盘上记录的物理顺序,而这通常遵循表的聚簇索引。但是这个顺序并不是一成不变的,特别是涉及连接时。如果连接匹配具有聚簇索引以外的索引的列,则可能会根据该索引对结果进行排序。最后,除非你给它一个ORDER BY子句,否则Sql Server将以它认为最快的顺序返回结果。

您可以通过将您的更新查询转换为选择查询来玩这个,这样您就可以看到结果了。注意哪个记录首先出现,哪个记录最后出现在目标表的每个记录的源表中。将其与更新查询的结果进行比较。然后再次使用索引并再次检查结果,看看你得到了什么。

当然,这里可能很棘手,因为UPDATE语句不允许使用ORDER BY子句,所以不管你发现什么,你都应该写连接,使它与目标表1:1匹配。您可能会发现APPLY运算符对实现此目标很有用,您可以使用它来有效地加入另一个表并保证连接只匹配一个记录。

答案 1 :(得分:0)

该选择不是确定性的,可以是任何源行。

您可以尝试

DECLARE @Source TABLE(Match INT, Data INT);

INSERT INTO @Source
VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4);

DECLARE @Destination TABLE(Match INT, Data INT);

INSERT INTO @Destination
VALUES
(1, NULL);


UPDATE Destination
SET    Data = Source.Data
FROM   @Destination Destination
       INNER JOIN @Source Source
               ON Destination.Match = Source.Match; 

SELECT *
FROM @Destination;

并查看实际的执行计划。我看到以下内容。

enter image description here

@Destination的输出列为Bmk1000, MatchBmk1000是一个内部行标识符(在此示例中由于缺少聚集索引而在此处使用),并且对于从@Destination发出的每一行都将是不同的(如果有多个)。

然后将单行连接到@Source中的四个匹配行上,并将得到的四行传递到流聚合中。

该流按Bmk1000进行分组,并将多个匹配的行折叠为1。此聚合执行的操作为ANY(@Source.[Data])

ANY聚合是TSQL本身不可用的internal aggregate function。无法保证将选择四个源行中的哪一个。

最后,每组的单行输入UPDATE运算符,以使用ANY聚合返回的任何值更新该行。

如果想要确定性的结果,则可以自己使用聚合函数...

WITH GroupedSource AS
(
SELECT Match,
       MAX(Data) AS Data
FROM @Source
GROUP BY Match
)
UPDATE Destination
SET    Data = Source.Data
FROM   @Destination Destination
       INNER JOIN GroupedSource Source
               ON Destination.Match = Source.Match; 

或使用ROW_NUMBER ...

WITH RankedSource AS
(
SELECT Match,
      Data,
      ROW_NUMBER() OVER (PARTITION BY Match ORDER BY Data DESC) AS RN
FROM @Source
)
UPDATE Destination
SET    Data = Source.Data
FROM   @Destination Destination
       INNER JOIN RankedSource Source
               ON Destination.Match = Source.Match
WHERE RN = 1; 

后一种形式通常更有用,因为如果需要设置多个列,这将确保使用的所有值都来自同一源行。为了具有确定性,partition byorder by列的组合应该是唯一的。