2013-02-11 41 views
43

我有一个表,名为energydataSQL MERGE语句来更新数据

它只有三列

(webmeterID, DateTime, kWh) 

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

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

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

+0

'temp_energydata'中是否有不在'energydata'中的记录? – 2013-02-11 06:15:58

回答

96

假设你想要一个实际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; 

因为这已经变得有点儿了e受欢迎,我觉得我应该扩大这个答案一些警告要注意。

首先,有几个博客报告concurrency issues with the MERGE statement。这可以在很大程度上通过指定HOLDLOCKSERIALIZABLE锁提示来工作围绕:

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

你也可以做到同样的事情更严格的事务隔离级别。

several other known issuesMERGE。据我所知,其中大部分都不是常见的问题,或者可以像上面一样使用相同的锁定提示,但我没有对它们进行测试。

事实上,即使我自己从来没有对MERGE声明有任何问题,我现在总是使用WITH (HOLDLOCK)提示,并且我倾向于仅在最直接的情况下使用该声明。

+2

在这种情况下,可能需要谨慎使用'NOT MATCHED BY SOURCE'子句。如果'temp_energydata'仅包含'energydata'中成员子集的更新,则您的第二个MERGE将删除临时集中找不到的所有**成员的数据。 – 2013-02-11 06:33:18

+1

@AndriyM这就是为什么我说“如果你还想删除目标中不在源代码中的记录”。我不确定这会如何混淆? – 2013-02-11 06:35:01

+0

好吧,也许不会让人困惑,但对于一个没有经验的人来说,它可能并不是完全明显的,当他们想要使用temp集更新主表中的行子集(特别是成员子集)时,删除的行也会包含那些不应该被更新的成员。不过,我并不坚持(可能不明显),因为我可能只是在那里过于谨慎,所以如果你这么想的话,请忽略我的评论。 – 2013-02-11 06:43:40

3

如果您需要根据在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(最好带有一个语句),那么您应该确定使用培根位给出的答案。

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

这很可能会覆盖'energydata'中的计量读数,而不是'temp_energydata'中的计数读数,这可能是令人惊讶和不期望的结果。 – peterm 2013-02-11 07:05:40

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

这很可能会覆盖'energydata'中的电表读数,而不是'temp_energydata'中的日期,这可能是令人惊讶和不期望的结果。 – peterm 2013-02-11 07:05:22

-6

正确的做法是:

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

如果'temp_energydata'中有新记录,则不应该。当然,你可以添加一个'INSERT INTO ... SELECT * FROM ...旧的LEFT JOIN新的WHERE old.foo IS NULL'(在UPDATE之前或之后),但它是两个语句,如果有足够的数据执行时间可能会导致问题很长时间,除非你锁定桌子,如果你这样做,你可能会激怒用户(没有足够的空间来进入所有场景)。 所有这一切说,我首先更新然后插入(反之亦然)我自己,但它不回答OP的问题。 – 2016-03-30 20:34:13

11

我经常用培根位伟大的答案,我只是不能记住语法。

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

您还可以将您的USING子句增强为完整的SELECT语句。如果查询很简单,这可以很好地工作,但如果查询有超过1-2个表,我就看到了非常糟糕的执行计划。在这种情况下,我会按照您的示例使用#temp表或CTE – 2017-09-01 08:51:52