SQL MERGE语句更新数据


69

我有一个表,数据名为 energydata

它只有三列

(webmeterID, DateTime, kWh)

我在表中有一组新的更新数据temp_energydata

DateTimewebmeterID保持不变。但是kWh值需要从temp_energydata表中更新。

我该如何以正确的方式编写T-SQL?


是否有记录在temp_energydata不在energydata
Nick.McDermaid

Answers:


134

假设您需要一个实际的SQL ServerMERGE语句:

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
USING dbo.temp_energydata AS source
    ON target.webmeterID = source.webmeterID
    AND target.DateTime = source.DateTime
WHEN MATCHED THEN 
    UPDATE SET target.kWh = source.kWh
WHEN NOT MATCHED BY TARGET THEN
    INSERT (webmeterID, DateTime, kWh)
    VALUES (source.webmeterID, source.DateTime, source.kWh);

如果您还想删除目标中不在源中的记录:

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
USING dbo.temp_energydata AS source
    ON target.webmeterID = source.webmeterID
    AND target.DateTime = source.DateTime
WHEN MATCHED THEN 
    UPDATE SET target.kWh = source.kWh
WHEN NOT MATCHED BY TARGET THEN
    INSERT (webmeterID, DateTime, kWh)
    VALUES (source.webmeterID, source.DateTime, source.kWh)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE;

因为这已经变得越来越流行了,所以我觉得我应该扩大一些注意事项以引起注意。

首先,有几个博客报告了旧版本SQL Server中MERGE语句的并发问题。我不知道此问题是否在以后的版本中得到解决。无论哪种方式,都可以通过指定HOLDLOCKSERIALIZABLE锁定提示来解决:

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
[...]

您还可以使用更严格的事务隔离级别来完成同一件事。

还有其他一些已知问题MERGE。(请注意,由于Microsoft对Connect进行了核对,并且未将旧系统中的问题链接到新系统中的问题,因此这些较旧的问题很难找到。谢谢Microsoft!)据我所知,大多数都不常见问题或可以使用与上述相同的锁定提示来解决,但我尚未对其进行测试。

实际上,即使我自己对MERGE语句从未遇到任何问题,但我现在总是使用WITH (HOLDLOCK)提示,并且我更喜欢仅在最简单的情况下使用该语句。


5
NOT MATCHED BY SOURCE在这种情况下,可能需要谨慎使用该子句。如果temp_energydata仅包含中的一部分成员的更新energydata,则您的第二个MERGE将删除在临时集中找不到的所有成员的数据。
Andriy M

3
@AndriyM这就是为什么我说“如果您还想删除目标中不在源中的记录”的原因。我不确定这会造成什么混乱?
培根片

好吧,也许没有引起混淆,但是对于一个没有经验的人,当他们想使用临时集来更新主表中的行的子集(尤其是成员的子集)时,可能并不十分清楚删除还将包括那些不应该更新的成员。不过,我并不是要坚持(可能并不明显),因为我可能在那里太谨慎了,因此,如果您这样认为,请不要理会我的评论。
Andriy M

对我来说是完美的解决方案。我的临时数据也包含新记录。我不需要删除目标中不在源中的记录。
user1745767

2
合并语句末尾是否缺少分号?
阿里

14

我经常用“培根钻头”很好的答案,因为我不记得语法。

但是我通常会添加一个CTE,以使DELETE部分更有用,因为您经常希望仅将合并应用于目标表的一部分。

WITH target as (
    SELECT * FROM dbo.energydate WHERE DateTime > GETDATE()
)
MERGE INTO target WITH (HOLDLOCK)
USING dbo.temp_energydata AS source
    ON target.webmeterID = source.webmeterID
    AND target.DateTime = source.DateTime
WHEN MATCHED THEN 
    UPDATE SET target.kWh = source.kWh
WHEN NOT MATCHED BY TARGET THEN
    INSERT (webmeterID, DateTime, kWh)
    VALUES (source.webmeterID, source.DateTime, source.kWh)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE

2
您还可以将USING子句增强为完整的SELECT语句。如果查询很简单,则此方法效果很好,但如果查询具有超过1-2个表,则执行计划非常糟糕。在这种情况下,按照您的示例,我将使用#temp表或CTE
Henrik Staun Poulsen

7

如果您只需要energydata基于中的数据更新记录temp_energydata,并假设其中temp_enerydata不包含任何新记录,请尝试以下操作:

UPDATE e SET e.kWh = t.kWh
  FROM energydata e INNER JOIN 
       temp_energydata t ON e.webmeterID = t.webmeterID AND 
                            e.DateTime = t.DateTime

这是工作的sqlfiddle

但是,如果temp_energydata包含新记录,并且您energydata最好将其插入一个语句中,那么您绝对应该使用培根比特斯给出的答案。


这更直接地回答了实际问题,看起来像是XY问题-似乎他们并不是从字面上要求MERGE操作,而是如何将数据从一个表合并到另一个表中(UPDATE如此处所示)。
bsplosion,

0
UPDATE ed
SET ed.kWh = ted.kWh
FROM energydata ed
INNER JOIN temp_energydata ted ON ted.webmeterID = ed.webmeterID

energydata对于以外的日期temp_energydata,这很可能会覆盖其中的电表读数,这可能会带来意想不到的意外结果。
peterm

0
Update energydata set energydata.kWh = temp.kWh 
where energydata.webmeterID = (select webmeterID from temp_energydata as temp) 

energydata对于以外的日期temp_energydata,这很可能会覆盖其中的电表读数,这可能会带来意想不到的意外结果。
peterm

-7

正确的方法是:

UPDATE test1
INNER JOIN test2 ON (test1.id = test2.id)
SET test1.data = test2.data

4
如果中有NEW记录,则不会temp_energydata。当然,您可以添加一个INSERT INTO ... SELECT * FROM ... old LEFT JOIN new WHERE old.foo IS NULL(在UPDATE之前或之后),但是它是两个语句,并且如果有足够的数据,则执行时间可能会足够长,从而导致出现问题,除非您锁定该表并且这样做可能会激怒用户(这里没有足够的空间来容纳所有方案)。说了这么多,我自己先进行UPDATE,然后进行INSERT(反之亦然),但它不能回答OP的问题。
安德鲁·斯蒂兹
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.