【问题标题】:SQL MERGE statement to update dataSQL MERGE 语句更新数据
【发布时间】:2013-01-26 05:42:05
【问题描述】:

我有一个名为energydata的数据表

它只有三列

(webmeterID, DateTime, kWh)

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

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

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

【问题讨论】:

  • temp_energydata 中是否有不在energydata 中的记录?

标签: sql-server-2008 tsql


【解决方案1】:

假设您想要一个实际的SQL Server MERGE 语句:

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 中报告了concurrency issues with the MERGE statement。我不知道这个问题是否在以后的版本中得到解决。无论哪种方式,这都可以通过指定HOLDLOCKSERIALIZABLE 锁定提示来解决:

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

您也可以使用更严格的事务隔离级别来完成同样的事情。

several other known issuesMERGE。 (请注意,由于 Microsoft 核对 Connect 并且没有将旧系统中的问题与新系统中的问题联系起来,因此这些旧问题很难追查。谢谢,Microsoft!)据我所知,它们中的大多数并不常见问题或可以使用与上述相同的锁定提示来解决,但我没有测试它们。

事实上,尽管我自己从未对MERGE 语句有任何问题,但我现在总是使用WITH (HOLDLOCK) 提示,并且我更喜欢仅在最直接的情况下使用该语句。

【讨论】:

  • 在这种情况下可能需要谨慎使用NOT MATCHED BY SOURCE 子句。如果temp_energydata 仅包含energydata 中一部分成员的更新,则您的第二次合并将删除临时集中未找到的所有 成员的数据。
  • @AndriyM 这就是为什么我说“如果您还想删除目标中不在源中的记录”。我不确定这会造成什么混乱?
  • 好吧,也许并不令人困惑,但是对于没有经验的人来说,可能并不完全明显,当他们想要使用临时集来更新行的子集时 (特别是成员的子集)在主表中,删除的行还将包括那些不应该更新的成员。不过,我并不坚持(这可能并不明显),因为我可能会在这方面过于谨慎,所以如果你这么认为,请忽略我的评论。
  • 对我来说是完美的解决方案。我的临时数据也确实包含新记录。我不需要删除目标中不在源中的记录。
  • merge 语句末尾是否缺少分号?
【解决方案2】:

我经常使用 Bacon Bits 很好的答案,因为我无法记住语法。

但我通常添加一个 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

【讨论】:

  • 您还可以将 USING 子句增强为完整的 SELECT 语句。如果查询很简单,这可以正常工作,但如果查询有超过 1-2 个表,我会看到非常糟糕的执行计划。在这种情况下,根据您的示例,我将使用#temp 表或 CTE
【解决方案3】:

正确的方法是:

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

【讨论】:

  • 如果temp_energydata 中有新记录,则不会。当然,您可以添加一个INSERT INTO ... SELECT * FROM ... old LEFT JOIN new WHERE old.foo IS NULL(在 UPDATE 之前或之后),但它是两个语句,如果有足够的数据,执行时间可能足够长而导致问题,除非您锁定表,如果您这样做,您可能激怒用户(这里没有足够的空间进入所有场景)。综上所述,我自己更喜欢更新然后插入(反之亦然),但它没有回答 OP 的问题。
【解决方案4】:

如果您只需要根据temp_energydata 中的数据更新您在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,最好使用一个语句,那么您绝对应该选择 Bacon Bits 给出的答案。

【讨论】:

  • 这更直接地回答了实际问题,这看起来像一个 XY 问题 - 看起来他们实际上并不是在要求 MERGE 操作,而是如何将一个表中的数据合并到另一个(这只是一个UPDATE,如此处所示)。
【解决方案5】:
Update energydata set energydata.kWh = temp.kWh 
where energydata.webmeterID = (select webmeterID from temp_energydata as temp) 

【讨论】:

  • 这很可能会覆盖energydata 中除temp_energydata 之外的日期的仪表读数,这可能会导致令人惊讶和不希望的结果。
【解决方案6】:
UPDATE ed
SET ed.kWh = ted.kWh
FROM energydata ed
INNER JOIN temp_energydata ted ON ted.webmeterID = ed.webmeterID

【讨论】:

  • 这很可能会覆盖energydata 中除temp_energydata 之外的日期的仪表读数,这可能会导致令人惊讶和不希望的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-11
  • 2021-01-06
  • 2014-11-08
  • 1970-01-01
  • 2013-02-11
  • 2023-03-24
  • 1970-01-01
相关资源
最近更新 更多