Вставить обновление хранимой процедуры на SQL Server - PullRequest
99 голосов
/ 17 августа 2008

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

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

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

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

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

Ответы [ 9 ]

59 голосов
/ 17 августа 2008

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

Важность UPSERT - от sqlservercentral.com :

Для каждого обновления в вышеупомянутом случае мы удаляем одно дополнительное чтение из таблицы, если мы используйте UPSERT вместо EXISTS. К сожалению для вставки, оба Методы UPSERT и IF EXISTS используют такое же количество чтений на столе. Поэтому проверка на существование должно быть сделано только тогда, когда есть очень веская причина, чтобы оправдать дополнительный ввод / вывод. Оптимизированный способ делать вещи, чтобы убедиться, что вы мало читать по возможности на БД.

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

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

49 голосов
/ 11 октября 2008

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

Для быстрого ответа попробуйте следующий шаблон. Это будет хорошо работать на SQL 2000 и выше. SQL 2005 дает вам обработку ошибок, которая открывает другие опции, а SQL 2008 дает вам команду MERGE.

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
10 голосов
/ 25 августа 2008

Если использовать с 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

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

Добавьте, как уже предлагалось, MERGE следует использовать там, где это возможно.

6 голосов
/ 17 сентября 2008

Вам нужно не только запустить его в транзакции, но и высокий уровень изоляции. Фактически, уровень изоляции по умолчанию - Read Commited, и этот код должен быть Serializable.

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

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

6 голосов
/ 17 августа 2008
Между прочим,

MERGE - одна из новых функций в SQL Server 2008.

5 голосов
/ 03 сентября 2008

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

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

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

3 голосов
/ 30 августа 2008

Большой поклонник UPSERT, действительно сокращает код для управления. Вот еще один способ сделать это: один из входных параметров - это идентификатор, если идентификатор равен NULL или 0, вы знаете, что это INSERT, в противном случае это обновление. Предполагается, что приложение знает, есть ли идентификатор, поэтому не будет работать во всех ситуациях, но при этом сократит количество выполнений пополам.

1 голос
/ 01 мая 2012

Модифицированный пост Димы Маленко:

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, и, если возможно, исправляем их внутренне.

1 голос
/ 02 сентября 2008

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

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

...