SQL MERGE语句来更新数据
我有一个表,名为energydata
SQL MERGE语句来更新数据
它只有三列
(webmeterID, DateTime, kWh)
我有一组新的数据更新表中的数据temp_energydata
。
DateTime
和webmeterID
保持不变。但kWh
值需要从temp_energydata
表更新。
如何以正确的方式编写T-SQL?
假设你想要一个实际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。这可以在很大程度上通过指定HOLDLOCK
或SERIALIZABLE
锁提示来工作围绕:
MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
[...]
你也可以做到同样的事情更严格的事务隔离级别。
有several other known issues与MERGE
。据我所知,其中大部分都不是常见的问题,或者可以像上面一样使用相同的锁定提示,但我没有对它们进行测试。
事实上,即使我自己从来没有对MERGE
声明有任何问题,我现在总是使用WITH (HOLDLOCK)
提示,并且我倾向于仅在最直接的情况下使用该声明。
在这种情况下,可能需要谨慎使用'NOT MATCHED BY SOURCE'子句。如果'temp_energydata'仅包含'energydata'中成员子集的更新,则您的第二个MERGE将删除临时集中找不到的所有**成员的数据。 – 2013-02-11 06:33:18
@AndriyM这就是为什么我说“如果你还想删除目标中不在源代码中的记录”。我不确定这会如何混淆? – 2013-02-11 06:35:01
好吧,也许不会让人困惑,但对于一个没有经验的人来说,它可能并不是完全明显的,当他们想要使用temp集更新主表中的行子集(特别是成员子集)时,删除的行也会包含那些不应该被更新的成员。不过,我并不坚持(可能不明显),因为我可能只是在那里过于谨慎,所以如果你这么想的话,请忽略我的评论。 – 2013-02-11 06:43:40
如果您需要根据在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
(最好带有一个语句),那么您应该确定使用培根位给出的答案。
UPDATE ed
SET ed.kWh = ted.kWh
FROM energydata ed
INNER JOIN temp_energydata ted ON ted.webmeterID = ed.webmeterID
这很可能会覆盖'energydata'中的计量读数,而不是'temp_energydata'中的计数读数,这可能是令人惊讶和不期望的结果。 – peterm 2013-02-11 07:05:40
Update energydata set energydata.kWh = temp.kWh
where energydata.webmeterID = (select webmeterID from temp_energydata as temp)
这很可能会覆盖'energydata'中的电表读数,而不是'temp_energydata'中的日期,这可能是令人惊讶和不期望的结果。 – peterm 2013-02-11 07:05:22
正确的做法是:
UPDATE test1
INNER JOIN test2 ON (test1.id = test2.id)
SET test1.data = test2.data
如果'temp_energydata'中有新记录,则不应该。当然,你可以添加一个'INSERT INTO ... SELECT * FROM ...旧的LEFT JOIN新的WHERE old.foo IS NULL'(在UPDATE之前或之后),但它是两个语句,如果有足够的数据执行时间可能会导致问题很长时间,除非你锁定桌子,如果你这样做,你可能会激怒用户(没有足够的空间来进入所有场景)。 所有这一切说,我首先更新然后插入(反之亦然)我自己,但它不回答OP的问题。 – 2016-03-30 20:34: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
您还可以将您的USING子句增强为完整的SELECT语句。如果查询很简单,这可以很好地工作,但如果查询有超过1-2个表,我就看到了非常糟糕的执行计划。在这种情况下,我会按照您的示例使用#temp表或CTE – 2017-09-01 08:51:52
'temp_energydata'中是否有不在'energydata'中的记录? – 2013-02-11 06:15:58