Решения для вставки или обновления на SQL Server - PullRequest
539 голосов
/ 20 сентября 2008

Предположим, что структура таблицы MyTable(KEY, datafield1, datafield2...).

Часто я хочу либо обновить существующую запись, либо вставить новую запись, если она не существует.

По существу:

IF (key exists)
  run update command
ELSE
  run insert command

Какой самый лучший способ написать это?

Ответы [ 21 ]

3 голосов
/ 08 ноября 2015

Это зависит от модели использования. Нужно смотреть на общую картину использования, не теряясь в деталях. Например, если шаблон использования обновлений составляет 99% после создания записи, то наилучшим решением будет «UPSERT».

После первой вставки (попадания) будут все обновления одного оператора, без ifs или buts. Условие 'where' на вставке необходимо, иначе оно вставит дубликаты, и вам не нужно заниматься блокировкой.

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END
3 голосов
/ 20 сентября 2008

В SQL Server 2008 вы можете использовать инструкцию MERGE

2 голосов
/ 12 октября 2008

MS SQL Server 2008 представляет оператор MERGE, который, как я считаю, является частью стандарта SQL: 2003. Как показали многие, не так уж и сложно обрабатывать случаи с одной строкой, но при работе с большими наборами данных нужен курсор со всеми возникающими проблемами производительности. Оператор MERGE будет очень полезным дополнением при работе с большими наборами данных.

1 голос
/ 20 июля 2011

Действительно ли условия гонки имеют значение, если вы сначала попробуете обновление, а затем вставку? Допустим, у вас есть два потока, которые хотят установить значение для ключа ключ :

Тема 1: значение = 1
Поток 2: значение = 2

Пример сценария состояния гонки

  1. ключ не определен
  2. Тема 1 завершается с ошибкой
  3. Тема 2 завершается с ошибкой
  4. Ровно один из потока 1 или потока 2 успешно выполняет вставку. Например. нить 1
  5. Другой поток завершается с ошибкой со вставкой (с ошибкой дублирующий ключ) - поток 2.

    • Результат: «Первый» из двух протекторов, который нужно вставить, определяет значение.
    • Требуемый результат: последний из 2 потоков для записи данных (обновление или вставка) должен решить значение

Но; в многопоточной среде планировщик ОС определяет порядок выполнения потока - в приведенном выше сценарии, где мы имеем это условие гонки, именно ОС определила последовательность выполнения. Т.е. неправильно говорить, что «поток 1» или «поток 2» был «первым» с точки зрения системы.

Когда время выполнения так близко для потока 1 и потока 2, результат условия гонки не имеет значения. Единственное требование должно состоять в том, чтобы один из потоков определял результирующее значение.

Для реализации: Если обновление, сопровождаемое вставкой, приводит к ошибке «дубликат ключа», это следует рассматривать как успех.

Кроме того, конечно, никогда не следует предполагать, что значение в базе данных совпадает со значением, которое вы написали последним.

1 голос
/ 21 июля 2010

Прежде чем все перейдут на HOLDLOCK-ы из-за страха от этих яростных пользователей, которые напрямую запускают ваши sprocs :-), позвольте мне отметить, что вы должны гарантировать уникальность новых PK-ов по дизайну (идентификационные ключи генераторы последовательностей в Oracle, уникальные индексы для внешних идентификаторов, запросы, охватываемые индексами). Это альфа и омега вопроса. Если у вас этого нет, то никакие HOLDLOCK-ы вселенной не спасут вас, и если у вас это есть, вам не нужно ничего, кроме UPDLOCK, при первом выборе (или сначала использовать обновление).

Sprocs обычно работают в очень контролируемых условиях и в предположении доверенного абонента (средний уровень). Это означает, что если простой паттерн upsert (update + insert или merge) когда-либо увидит дублирующую PK, это означает ошибку в вашем среднем уровне или дизайне таблицы, и хорошо, что SQL в таком случае выкочит ошибку и отклонит запись. Размещение HOLDLOCK в этом случае равнозначно исключениям при приеме пищи и приему потенциально ошибочных данных, помимо снижения производительности.

Сказав, что, используя MERGE или UPDATE, INSERT проще на вашем сервере и менее подвержен ошибкам, так как вам не нужно добавлять (UPDLOCK) для первого выбора. Кроме того, если вы делаете вставки / обновления небольшими партиями, вам нужно знать свои данные, чтобы решить, подходит ли транзакция или нет. Если это просто набор несвязанных записей, то дополнительная «обволакивающая» транзакция будет пагубной.

0 голосов
/ 20 сентября 2008

Сделайте выбор, если вы получите результат, обновите его, если нет, создайте его.

0 голосов
/ 03 июля 2015

Я попробовал приведенное ниже решение, и оно работает для меня, когда возникает параллельный запрос на оператор вставки.

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran
0 голосов
/ 20 сентября 2008

Выполнение if существует ... else ... подразумевает выполнение как минимум двух запросов (один для проверки, один для выполнения действия). При следующем подходе требуется только один, где запись существует, два, если требуется вставка:

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')
0 голосов
/ 20 сентября 2008

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

FirstSP:
If Exists
   Call SecondSP (UpdateProc)
Else
   Call ThirdSP (InsertProc)

Так вот, я не очень часто следую своему собственному совету, поэтому примите его с крошкой соли.

0 голосов
/ 13 августа 2012

Вы можете использовать этот запрос. Работа во всех выпусках SQL Server. Это просто и понятно. Но вам нужно использовать 2 запроса. Вы можете использовать, если вы не можете использовать MERGE

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

ПРИМЕЧАНИЕ: Пожалуйста, объясните отрицательный ответ

...