Оператор SQL MERGE для обновления данных

У меня есть таблица с данными с именем energydata

у него всего три столбца

(webmeterID, DateTime, kWh)

У меня есть новый набор обновленных данных в таблице temp_energydata.

DateTime и webmeterID остаются прежними. Но значения kWh нуждаются в обновлении из таблицы temp_energydata.

Как правильно написать T-SQL для этого?


person user1745767    schedule 11.02.2013    source источник
comment
Есть ли в temp_energydata записи, которых нет в energydata?   -  person Nick.McDermaid    schedule 11.02.2013


Ответы (6)


Предположим, вам нужен фактический оператор 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;

Поскольку это стало немного более популярным, я чувствую, что должен немного расширить этот ответ с некоторыми оговорками, о которых следует знать.

Во-первых, есть несколько блогов, в которых сообщается проблемы параллелизма с оператором MERGE в старых версиях SQL Server. Я не знаю, поднимался ли этот вопрос когда-либо в более поздних изданиях. В любом случае, это можно обойти, указав подсказку блокировки HOLDLOCK или SERIALIZABLE:

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

Вы также можете сделать то же самое с более строгими уровнями изоляции транзакций.

Существует несколько других известных проблем. с MERGE. (Обратите внимание, что, поскольку Microsoft отключила Connect и не связывала проблемы в старой системе с проблемами в новой системе, эти старые проблемы трудно отследить. Спасибо, Microsoft!) Насколько я могу судить, большинство из них не распространены. проблемы, или их можно обойти с помощью тех же подсказок блокировки, что и выше, но я их не проверял.

Как бы то ни было, хотя у меня никогда не было проблем с оператором MERGE, теперь я всегда использую подсказку WITH (HOLDLOCK) и предпочитаю использовать оператор только в самых простых случаях.

person Bacon Bits    schedule 11.02.2013
comment
Предложение NOT MATCHED BY SOURCE в этом случае может потребоваться использовать с осторожностью. Если temp_energydata содержит обновления только для подмножества элементов в energydata, ваше второе MERGE удалит данные всех элементов, не найденных во временном наборе. - person Andriy M; 11.02.2013
comment
@AndriyM Вот почему я сказал, если вы также хотите удалить записи в цели, которых нет в источнике. Я не уверен, как это может сбить с толку? - person Bacon Bits; 11.02.2013
comment
Что ж, возможно, это не сбивает с толку, но для неопытного человека может быть не совсем очевидно, что, когда они хотят использовать временный набор для обновления подмножества строк (в частности, подмножества членов) в основной таблице удаленные строки будут также включать те члены, которые не должны были обновляться. Я не настаиваю (что это может быть неочевидно), поскольку я мог бы просто быть слишком осторожным, поэтому, пожалуйста, не обращайте внимания на мой комментарий, если вы так думаете. - person Andriy M; 11.02.2013
comment
Идеальное решение для меня. Мои временные данные также содержали новые записи. Мне не нужно было удалять записи в цели, которых не было в источнике. - person user1745767; 12.02.2013
comment
В конце оператора слияния отсутствует точка с запятой? - person Ali; 22.03.2017
comment
Спасибо @BaconBits, у меня появилось несколько красных волнистых линий. Intellisense не кажется настолько надежным для запросов MERGE. - person Ali; 11.04.2017
comment
@Ali О, нет, ты прав. Операторы MERGE явно должны заканчиваться точкой с запятой! Анализатор запросов, который я использовал, просто добавил их автоматически. - person Bacon Bits; 17.04.2017
comment
@BaconBits А, хорошо. Спасибо за продолжение. Хорошо знать. - person Ali; 19.04.2017

Я часто использовал отличный ответ 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
person Patrick Fromberg    schedule 05.09.2016
comment
вы также можете расширить предложение USING до полного оператора SELECT. Это прекрасно работает, если запрос простой, но я видел очень плохие планы выполнения, если в запросе было более 1-2 таблиц. В этом случае я бы использовал таблицу #temp или CTE, как в вашем примере. - person Henrik Staun Poulsen; 01.09.2017

Если вам нужно просто обновить записи в 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

Здесь работает sqlfiddle

Но если temp_energydata содержит новые записи и вам нужно вставить их в energydata желательно одним оператором, то вам обязательно следует воспользоваться ответом, который дал Bacon Bits.

person peterm    schedule 11.02.2013
comment
Это более прямо отвечает на фактический вопрос, который выглядит как проблема XY - не похоже, что они буквально просили об операции MERGE, а скорее о том, как объединить данные из одной таблицы в другую (это просто UPDATE, как показано здесь ). - person bsplosion; 06.05.2019

ПРАВИЛЬНЫЙ ПУТЬ:

UPDATE test1
INNER JOIN test2 ON (test1.id = test2.id)
SET test1.data = test2.data
person Jaxx0rr    schedule 05.05.2014
comment
Нет, если в temp_energydata есть НОВЫЕ записи. Конечно, вы можете добавить INSERT INTO ... SELECT * FROM ... old LEFT JOIN new WHERE old.foo IS NULL (до или после ОБНОВЛЕНИЯ), но это два оператора, и если данных достаточно, время выполнения может быть достаточно долгим, чтобы вызвать проблемы, если вы не заблокируете таблицу, и если вы сделаете это, вы, вероятно, придете в ярость. пользователей (здесь недостаточно места для описания всех сценариев). Все это говорит о том, что я ПРЕДПОЧИТАЮ ОБНОВЛЕНИЕ, а затем ВСТАВЛЯЮ (или наоборот) себя, но это не отвечает на вопрос ОП. - person Andrew Steitz; 30.03.2016

person    schedule
comment
Это, скорее всего, перезапишет показания счетчика в energydata для дат, отличных от temp_energydata, что может привести к неожиданному и нежелательному результату. - person peterm; 11.02.2013

person    schedule
comment
Это, скорее всего, перезапишет показания счетчика в energydata для дат, отличных от temp_energydata, что может привести к неожиданному и нежелательному результату. - person peterm; 11.02.2013