用于更新数据的SQL MERGE语句

use*_*767 59 t-sql sql-server-2008

我有一个名为数据的表 energydata

它只有三列

(webmeterID, DateTime, kWh)
Run Code Online (Sandbox Code Playgroud)

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

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

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

Bac*_*its 117

假设您需要一个实际的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);
Run Code Online (Sandbox Code Playgroud)

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

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

因为这已经变得更受欢迎,我觉得我应该稍微扩展这个答案,并注意一些注意事项.

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

MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
[...]
Run Code Online (Sandbox Code Playgroud)

您还可以使用更具限制性的事务隔离级别来完成相同的操作.

还有其他一些已知问题MERGE.(请注意,由于微软认为Connect并没有将旧系统中的问题与新系统中的问题联系起来,因此很难追查这些旧问题.谢谢,微软!)据我所知,其中大多数都不常见问题或可以解决与上面相同的锁定提示,但我没有测试它们.

事实上,尽管我自己从来没有遇到任何问题,但MERGE我现在总是使用WITH (HOLDLOCK)提示,而我更喜欢只在最直接的情况下使用该语句.

  • @AndriyM 这就是为什么我说“如果您还想删除目标中不在源中的记录”。我不确定这会如何令人困惑? (4认同)
  • 在这种情况下,可能需要谨慎使用"NOT MATCHED BY SOURCE"条款.如果`temp_energydata`只包含`energydata`中成员子集的更新,那么你的第二个MERGE将删除**临时集中找不到的所有**成员的数据. (3认同)
  • 合并语句末尾是否缺少分号? (2认同)

Pat*_*erg 13

我经常使用培根比特很好的答案,因为我无法记住语法.

但我通常添加一个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
Run Code Online (Sandbox Code Playgroud)

  • 您还可以将 USING 子句增强为完整的 SELECT 语句。如果查询很简单,这很好用,但如果查询有 1-2 个以上的表,我会看到非常糟糕的执行计划。在这种情况下,我将根据您的示例使用 #temp 表或 CTE (2认同)

pet*_*erm 5

如果您只需要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
Run Code Online (Sandbox Code Playgroud)

这是工作的sqlfiddle

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