SQL MERGE statement to update data
Solution 1
Assuming you want an actual SQL Server MERGE
statement:
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);
If you also want to delete records in the target that aren't in the source:
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;
Because this has become a bit more popular, I feel like I should expand this answer a bit with some caveats to be aware of.
First, there are several blogs which report concurrency issues with the MERGE
statement in older versions of SQL Server. I do not know if this issue has ever been addressed in later editions. Either way, this can largely be worked around by specifying the HOLDLOCK
or SERIALIZABLE
lock hint:
MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
[...]
You can also accomplish the same thing with more restrictive transaction isolation levels.
There are several other known issues with MERGE
. (Note that since Microsoft nuked Connect and didn't link issues in the old system to issues in the new system, these older issues are hard to track down. Thanks, Microsoft!) From what I can tell, most of them are not common problems or can be worked around with the same locking hints as above, but I haven't tested them.
As it is, even though I've never had any problems with the MERGE
statement myself, I always use the WITH (HOLDLOCK)
hint now, and I prefer to use the statement only in the most straightforward of cases.
Solution 2
I often used Bacon Bits great answer as I just can not memorize the syntax.
But I usually add a CTE as an addition to make the DELETE part more useful because very often you will want to apply the merge only to a part of the target table.
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
Solution 3
If you need just update your records in energydata
based on data in temp_energydata
, assuming that temp_enerydata
doesn't contain any new records, then try this:
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
Here is working sqlfiddle
But if temp_energydata
contains new records and you need to insert it to energydata
preferably with one statement then you should definitely go with the answer that Bacon Bits gave.
user1745767
Updated on July 05, 2022Comments
-
user1745767 about 2 years
I've got a table with data named
energydata
it has just three columns
(webmeterID, DateTime, kWh)
I have a new set of updated data in a table
temp_energydata
.The
DateTime
and thewebmeterID
stay the same. But thekWh
values need updating fromtemp_energydata
table.How do I write the T-SQL for this the correct way?
-
Andriy M over 11 yearsThe
NOT MATCHED BY SOURCE
clause may need to be used with caution in this case. Iftemp_energydata
contains updates for only a subset of members inenergydata
, your second MERGE is going to delete the data of all members not found in the temporary set. -
Bacon Bits over 11 years@AndriyM Which is why I said "If you also want to delete records in the target that aren't in the source". I'm not sure how this would be confusing?
-
Andriy M over 11 yearsWell, perhaps not confusing but, to an inexperienced person, it might not be entirely obvious that, when they want to use the temp set to update a subset of rows (in particular, a subset of members) in the main table, the rows deleted will also include those members that weren't supposed to be updated. I'm not insisting (that it may not be obvious), though, as I might as well just be overcautious there, so please disregard my comment if you think so.
-
peterm over 11 yearsThat will most likely overwrite meter readings in
energydata
for dates other than those intemp_energydata
, which might by surprising and undesired outcome. -
peterm over 11 yearsThat will most likely overwrite meter readings in
energydata
for dates other than those intemp_energydata
, which might by surprising and undesired outcome. -
user1745767 over 11 yearsPerfect solution for me. My temp data did contain new records as well. I didn't need to delete records in the target that were not in the source.
-
Andrew Steitz over 8 yearsNot if there are NEW records in
temp_energydata
. Sure, you could add aINSERT INTO ... SELECT * FROM ... old LEFT JOIN new WHERE old.foo IS NULL
(before or after the UPDATE) but it is two statements and if there is enough data the execution time could be long enough to cause issues unless you LOCK the table and if you do that your are likely to infuriate users (not enough space here to go into all the scenarios). All that said, I PREFER the UPDATE then INSERT (or vice versa) myself but it does not answer the OP's question. -
Ali over 7 yearsIs there a semicolon missing at the end of the merge statement?
-
Ali about 7 yearsThanks @BaconBits, I was getting a few red squigglies. Intellisense doesn't seem to be so robust around MERGE queries.
-
Bacon Bits about 7 years@Ali Oh, no, you're right. MERGE statements explicitly need to be terminated with semicolons! The query analyzer I was using just added them automatically.
-
Ali about 7 years@BaconBits Ah, OK. Thanks for following up. Good to know.
-
Henrik Staun Poulsen almost 7 yearsyou can also enhance your USING clause to a full SELECT statement. This works fine if the query is simple, but I've seen very bad execution plans if the query had more than 1-2 tables. In this case, I would use a #temp table or a CTE, as per your example
-
bsplosion about 5 yearsThis more directly answers the actual question, which looks like an XY problem - it doesn't seem that they were literally asking for a
MERGE
operation, but rather how to merge data from one table into another (which is just anUPDATE
as shown here).