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

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

update myTable set [email protected], [email protected] where [email protected]
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

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

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

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

Ответ 1

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

Значение UPSERT - от sqlservercentral.com:

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

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

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

Ответ 2

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

Для быстрого ответа попробуйте следующий шаблон. Он отлично работает на 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

Ответ 3

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

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

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

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

Ответ 4

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

Ответ 5

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

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

Майн добавляет, что ошибка @@и проверка отката могут быть хорошей идеей.

Ответ 6

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

Ответ 7

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

if @@rowcount = 0 и @@error = 0

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

Ответ 8

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

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

Ответ 9

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

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