T-SQL вставить или обновить

у меня вопрос относительно производительности SQL Server.

Предположим у меня есть таблица persons со следующими столбцами: id, name, surname.

теперь я хочу вставить новую строку в эту таблицу. Правило следующее:

  1. если id отсутствует в таблице, затем вставьте строку.

  2. если id присутствует, затем обновить.

у меня есть два решения здесь:

первый:

update persons
  set id=@p_id, name=@p_name, surname=@p_surname
where id=@p_id
if @@ROWCOUNT = 0 
  insert into persons(id, name, surname)
  values (@p_id, @p_name, @p_surname)

второй:

if exists (select id from persons where id = @p_id)
  update persons
    set id=@p_id, name=@p_name, surname=@p_surname
  where id=@p_id
else
  insert into persons(id, name, surname)
  values (@p_id, @p_name, @p_surname)

Что такое лучший подход? Похоже, что во втором варианте, чтобы обновить строку, ее нужно искать два раза, тогда как в первом варианте - только один раз. Есть ли другие решения проблемы? Я с помощью MS SQL с 2000.

5 ответов


оба работают нормально, но я обычно использую вариант 2 (pre-mssql 2008), так как он читает немного более четко. Я бы тоже не стал говорить о выступлении здесь...Если это становится проблемой, вы можете использовать NOLOCK на exists предложения. Хотя, прежде чем вы начнете использовать nolock везде, убедитесь, что вы охватили все свои базы (индексы и архитектуру больших изображений). Если вы знаете, что будете обновлять каждый элемент более одного раза, то может потребоваться рассмотреть вариант 1.

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

Вариант 1 кажется хорошим. Однако, если вы находитесь на SQL Server 2008, вы также можете использовать слияние, который может выполнять хорошо для таких задач UPSERT.

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


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

http://sqlblogcasts.com/blogs/piotr_rodak/archive/2010/01/04/updlock-holdlock-and-deadlocks.aspx


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

begin tran
insert into persons (id)
select @p_id from persons
 where not exists (select * from persons where id = @p_id)

update persons
set name=@p_name, surname=@p_surname
where id = @p_id

commit

колонки name и surname должно быть nullable.

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

Edit: cleanup


вы можете просто использовать @@RowCount, чтобы увидеть, сделало ли обновление что-нибудь. Что-то вроде:

    UPDATE MyTable
       SET SomeData = 'Some Data' WHERE ID = 1
    IF @@ROWCOUNT = 0
      BEGIN
        INSERT MyTable
        SELECT 1, 'Some Data'       
      END