Вставить Обновление сохраненной процедуры на SQL Server

StackOverflow https://stackoverflow.com/questions/13540

Вопрос

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

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Моя логика, стоящая за написанием этого таким образом, заключается в том, что update выполнит неявный select с использованием предложения where, и если это вернет 0, то произойдет вставка.

Альтернативой выполнению этого способа было бы выполнить select, а затем, в зависимости от количества возвращенных строк, либо выполнить update, либо insert.Это я счел неэффективным, потому что, если вы хотите выполнить обновление, это вызовет 2 выбора (первый явный вызов select и второй неявный в where обновления).Если бы процедура выполняла вставку, то не было бы никакой разницы в эффективности.

Здрава ли здесь моя логика?Это то, как вы бы объединили вставку и обновление в сохраненный процесс?

Это было полезно?

Решение

Ваше предположение верно, это оптимальный способ сделать это, и он называется обновление / слияние.

Важность ОБНОВЛЕНИЯ - из sqlservercentral.com:

Для каждого обновления в упомянутом выше случае мы удаляем одно дополнительное чтение из таблицы, если мы используем UPSERT вместо EXISTS.К сожалению, для Insert оба метода UPSERT и IF EXISTS используют одинаковое количество операций чтения в таблице.Следовательно, проверка на наличие должна выполняться только тогда, когда есть очень веская причина для обоснования дополнительного ввода-вывода.Оптимальный способ сделать что-либо - убедиться, что у вас как можно меньше операций чтения в базе данных.

Лучшая стратегия - попытаться выполнить обновление .Если обновление не затронуло ни одной строки , вставьте.В большинстве случаев строка уже будет существовать, и потребуется только один ввод-вывод.

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

Другие советы

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

Для быстрого ответа попробуйте использовать следующий шаблон.Это будет нормально работать на SQL 2000 и выше.SQL 2005 предоставляет вам обработку ошибок, которая открывает другие параметры, а SQL 2008 предоставляет вам команду СЛИЯНИЯ.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

При использовании с SQL Server 2000/2005 исходный код должен быть заключен в транзакцию, чтобы убедиться, что данные остаются согласованными в параллельном сценарии.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Это повлечет за собой дополнительные затраты на производительность, но обеспечит целостность данных.

Добавить, как уже предлагалось, СЛИЯНИЕ следует использовать там, где это возможно.

Кстати, СЛИЯНИЕ - это одна из новых функций в SQL Server 2008.

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

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Возможно, хорошей идеей было бы добавить также проверку ошибок @@ и откат.

Если вы не выполняете слияние в SQL 2008, вы должны изменить его на:

если @@rowcount = 0 и @@error=0

в противном случае, если обновление по какой-либо причине завершится неудачно, оно попытается выполнить вставку после этого, потому что rowcount в сбойном операторе равен 0

Большой поклонник UPSERT, действительно сокращает объем кода для управления.Вот еще один способ, которым я это делаю:Одним из входных параметров является ID, если ID равен NULL или 0, вы знаете, что это ВСТАВКА, в противном случае это обновление.Предполагается, что приложение знает, есть ли идентификатор, поэтому не будет работать во всех ситуациях, но сократит время выполнения вдвое, если вы это сделаете.

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

В противном случае, если вы всегда выполняете вставку, если обновление не повлияло ни на какие записи, что произойдет, когда кто-то удалит запись до того, как вы запустите "UPSERT"?Теперь запись, которую вы пытались обновить, не существует, поэтому вместо нее будет создана запись.Вероятно, это не то поведение, которое вы искали.

Измененный пост Димы Маленко:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

Вы можете перехватить ошибку и отправить запись в таблицу с ошибкой вставки.
Мне нужно было это сделать, потому что мы берем все данные, отправляемые через WSDL, и, по возможности, исправляем их внутренне.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top