Решения для ВСТАВКИ ИЛИ ОБНОВЛЕНИЯ на SQL Server

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

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

По сути:

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

Как лучше всего это написать?

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

Aaron Bertrand 19.01.2014 00:42

Рассмотрите возможность использования оператора EXCEPT, который был представлен в SQL Server 2005.

Tarzan 16.10.2019 18:38
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
632
3
712 930
22
Перейти к ответу Данный вопрос помечен как решенный

Ответы 22

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

Это два обращения к базе данных.

Chris Cudmore 20.09.2008 19:04

Я не вижу в этом проблемы.

Clint Ecker 20.09.2008 19:14

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

Kev 20.09.2008 23:05

он также создает состояние гонки нет?

niico 02.05.2017 10:35

IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

Редактировать:

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

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

Eric Z Beard 21.09.2008 05:04

Сделайте UPSERT:

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

http://en.wikipedia.org/wiki/Upsert

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

Triynko 12.05.2010 10:12

@Triynko, я думаю, @Sam Saffron означал, что если два + потока чередуются в правильной последовательности, тогда sql server выдаст бросать ошибку, указывающую на нарушение первичного ключа имел бы. Оборачивание его в сериализуемую транзакцию - правильный способ предотвратить ошибки в приведенном выше наборе операторов.

EBarr 02.12.2010 02:12

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

Seph 19.01.2012 17:38

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

CashCow 23.02.2012 16:00
Ответ принят как подходящий

не забывайте о сделках. Производительность хорошая, но простой (ЕСЛИ СУЩЕСТВУЕТ ..) подход очень опасен. Когда несколько потоков будут пытаться выполнить вставку или обновление, вы можете легко получить нарушение первичного ключа.

Решения, предоставленные @Beau Crawford и @Esteban, демонстрируют общую идею, но подвержены ошибкам.

Чтобы избежать взаимоблокировок и нарушений PK, вы можете использовать что-то вроде этого:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

или же

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

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

Luke Bennett 20.09.2008 19:17

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

Luke Bennett 20.09.2008 19:26

Оба эти метода все еще могут потерпеть неудачу. Если два параллельных потока делают то же самое в одной строке, первый будет успешным, но вторая вставка не удастся из-за нарушения первичного ключа. Транзакция не гарантирует, что вставка будет успешной, даже если обновление не удалось из-за существования записи. Чтобы гарантировать успешное выполнение любого количества одновременных транзакций, вы ДОЛЖНЫ использовать блокировку.

Jean Vincent 28.07.2010 13:17

@aku по какой причине вы использовали подсказки таблицы ("with (xxxx)") вместо "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" непосредственно перед BEGIN TRAN?

EBarr 02.12.2010 03:19

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

CashCow 23.02.2012 15:59

@CashCow, последний выигрывает, это то, что должны делать INSERT или UPDATE: первый вставляет, второй обновляет запись. Добавление блокировки позволяет сделать это в очень короткие сроки, предотвращая ошибку.

Jean Vincent 29.02.2012 15:31

Также см. Ответ @zvolkov на этот вопрос stackoverflow.com/questions/1488355/…

Christian 05.07.2016 16:49

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

user8280126 01.10.2017 07:33

Выполнение if exists ... 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')

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

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

Я не очень часто следую своему собственному совету, так что относитесь к нему с недоверием.

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

Aaron Bertrand 19.01.2014 00:35

Если вы используете ADO.NET, DataAdapter справится с этим.

Если вы хотите справиться с этим самостоятельно, вот способ:

Убедитесь, что для ключевого столбца существует ограничение первичного ключа.

Затем вы:

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

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

... и выполнение вставки в первую очередь (зная, что иногда это не удается) дорого обходится для SQL Server. sqlperformance.com/2012/08/t-sql-queries/error-handling

Aaron Bertrand 19.01.2014 00:36

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

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

swasheck 27.01.2014 21:22

Очень старый, но было бы неплохо привести пример.

Matt McCabe 18.11.2015 16:41

Если вы хотите выполнить UPSERT более одной записи за раз, вы можете использовать оператор ANSI SQL: 2003 DML MERGE.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

Проверьте Имитация инструкции MERGE в SQL Server 2005.

В Oracle выполнение оператора MERGE I считать блокирует таблицу. То же самое происходит в SQL * Server?

Mike McAllister 21.09.2008 05:29

MERGE восприимчив к условиям гонки (см. weblogs.sqlteam.com/dang/archive/2009/01/31/…), если вы не заставите его удерживать блокировки сертификатов. Кроме того, взгляните на производительность MERGE в SQL Profiler ... я обнаружил, что он обычно медленнее и генерирует больше операций чтения, чем альтернативные решения.

EBarr 02.12.2010 03:20

@EBarr - Спасибо за ссылку на замки. Я обновил свой ответ, включив в него подсказку о блокировке.

Eric Weilnau 02.12.2010 18:45

Также проверьте mssqltips.com/sqlservertip/3074/…

Aaron Bertrand 18.01.2014 23:36

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

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

HLGEM 14.04.2009 22:15

Смотрите мой подробный ответ на очень похожий предыдущий вопрос

@ Бо Кроуфорд - хороший способ в SQL 2005 и ниже, хотя, если вы предоставляете репутацию, он должен перейти к первый парень ТАК это. Единственная проблема в том, что для вставок это еще две операции ввода-вывода.

MS Sql2008 представляет merge из стандарта SQL: 2003:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

Теперь это действительно всего одна операция ввода-вывода, но ужасный код :-(

@Ian Boyd - да, это синтаксис стандарта SQL: 2003, а не upsert, который решили поддерживать почти все другие поставщики баз данных. Синтаксис upsert - гораздо лучший способ сделать это, поэтому, по крайней мере, MS должна была его поддерживать - это не единственное нестандартное ключевое слово в T-SQL.

Keith 20.07.2011 16:38

документация не показывает предложение where как допустимый синтаксис: technet.microsoft.com/en-us/library/bb510625.aspx

Kip 14.10.2011 19:24

какие-либо комментарии к подсказке о блокировке в других ответах? (скоро узнаю, но если это рекомендуемый способ, рекомендую добавить его в ответ)

eglasius 19.01.2012 00:05

@eglasius это должно быть заключено в транзакцию, но это происходит как чтение для любого SQL insert или update. Использование merge - это одна операция, поэтому нет никаких шансов столкнуться с другим действием.

Keith 19.01.2012 12:38

См. Здесь weblogs.sqlteam.com/dang/archive/2009/01/31/… для ответа о том, как предотвратить условия гонки, вызывающие ошибки, которые могут возникнуть даже при использовании синтаксиса MERGE.

Seph 19.01.2012 17:35

@Seph, это настоящий сюрприз - что-то вроде провала Microsoft: -S Думаю, это означает, что вам нужен HOLDLOCK для операций слияния в ситуациях с высоким уровнем параллелизма.

Keith 19.01.2012 18:23

@Keith Я не согласен с вашим утверждением, что это уродливый код :)

theycallmemorty 28.02.2013 20:24

@theycallmemorty необходимо поддерживать ТРИ отдельных списка полей, это ненужный кошмар обслуживания, и это делает его уродливым. :-)

moodboom 22.04.2013 20:03

@moodboom - не только это; как UPSERT, так и UPDATE имеют поля в парах Set [field] = @value. INSERT имеет список полей и список значений, подходящих для пары полей, но не беда, когда у вас много полей.

Keith 22.04.2013 23:07

@Keith yep, MERGE просто отвратительно, когда вы просто хотите апсерта, который настолько прост и распространен. Не уверен, что думали дизайнеры ANSI и MSSQL. Может, через три-четыре года ... вздох ...

moodboom 22.04.2013 23:38

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

Triynko 09.11.2013 12:44

Редактор действительно добавил директиву HOLDLOCK, но OP откатил изменения.

palswim 15.11.2016 23:06

@palswim они сделали, но они также переформатировали его и добавили другие вещи. Я принципиально склонен откатывать такие изменения - извините, я пропустил там одно стоящее изменение. Если бы они просто добавили отсутствующую директиву with, я бы оставил ее, и я сделал это сейчас.

Keith 16.11.2016 10:34

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

Такие операторы Insert + Update обычно называются операторами «Upsert» и могут быть реализованы с помощью MERGE в SQL Server.

Здесь приведен очень хороший пример: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

Выше также описаны сценарии блокировки и параллелизма.

Я процитирую то же самое для справки:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;

Есть и другие вещи, о которых нужно беспокоиться с MERGE: mssqltips.com/sqlservertip/3074/…

Aaron Bertrand 18.01.2014 23:43

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

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

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

Если вы просто выполняете обновление, а затем вставляете его без какой-либо блокировки или повышенной изоляции, тогда два пользователя могут попытаться передать одни и те же данные обратно (я бы не стал считать это ошибкой на среднем уровне, если два пользователя попытались отправить точно такую ​​же информацию на в то же время - во многом зависит от контекста, не так ли?). Они оба входят в обновление, которое возвращает 0 строк для обоих, затем они оба пытаются вставить. Один выигрывает, другой - исключение. Это то, чего люди обычно стараются избегать.

Aaron Bertrand 19.01.2014 00:17

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

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

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

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

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

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

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

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

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

Если вы переходите по маршруту UPDATE if-no-rows-updated, а затем INSERT, подумайте о том, чтобы сначала выполнить INSERT, чтобы предотвратить состояние гонки (при условии отсутствия промежуточного DELETE)

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

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

Использование MERGE, вероятно, предпочтительнее для SQL2008 и более поздних версий.

Интересная идея, но неверный синтаксис. Для SELECT требуется FROM <table_source> и TOP 1 (если только выбранный table_source не имеет только 1 строку).

jk7 12.04.2016 22:03

Спасибо. Я изменил его на НЕ СУЩЕСТВУЕТ. Будет только одна совпадающая строка из-за теста на «ключ» согласно O / P (хотя, возможно, это должен быть ключ, состоящий из нескольких частей :))

Kristen 18.04.2016 09:22

Вы можете использовать этот запрос. Работает во всех редакциях 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

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

Я предполагаю отсутствие блокировки?

Zeek2 20.05.2019 11:26

Нет недостатка в блокировке ... Я использую "TRAN". У транзакций sql-сервера по умолчанию есть блокировка.

Victor Sanchez 20.05.2019 14:06

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

Даже при наличии этого «более простого» синтаксиса я все же предпочитаю этот подход (обработка ошибок для краткости опущена):

BEGIN TRANSACTION;

UPDATE dbo.table WITH (UPDLOCK, SERIALIZABLE) 
  SET ... WHERE PK = @PK;

IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END

COMMIT TRANSACTION;

Многие предложат такой способ:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
BEGIN
  INSERT ...
END
COMMIT TRANSACTION;

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

Другие предложат такой способ:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

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

Как насчет вставки / обновления ИЗ временной таблицы, которая вставляет / обновляет много записей?

user960567 12.05.2014 11:21

@ user960567 ну UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);

Aaron Bertrand 22.07.2016 02:47

приятно ответил спустя более 2 лет :)

user960567 22.07.2016 13:37

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

Aaron Bertrand 28.07.2016 06:00

@AaronBertrand Спасибо за это. Re: 2020-09-23 edit, ваш предпочтительный подход, описанный выше, обертывает транзакцию с помощью "ISOLATION LEVEL SERIALIZABLE", без подсказки таблицы UPDLOCK. связанная статья от 02.09.2020 использует две подсказки таблицы «UPDATE dbo.t WITH (UPDLOCK, SERIALIZABLE)» и не «ISOLATION LEVEL SERIALIZABLE» (что имеет смысл). Извините за глупый вопрос, но они эквивалентны или один подход лучше другого? Я предполагаю, что ваша последняя статья представляет ваш текущий предпочтительный подход. Спасибо.

iokevins 29.09.2020 00:55

@iokevins Я не могу придумать никакой разницы. Я на самом деле разорван в плане предпочтений, хотя я предпочитаю иметь подсказку на уровне запроса, я предпочитаю обратное, когда мы говорим, скажем, о применении подсказок NOLOCK к каждой таблице в запросе (в этом случае я предпочитаю один оператор SET, который будет исправлен позже).

Aaron Bertrand 29.09.2020 05:23

/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

Замените имена таблиц и полей на все, что вам нужно. Позаботьтесь о состоянии с помощью ON. Затем установите соответствующее значение (и тип) для переменных в строке DECLARE.

Ваше здоровье.

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

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

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

После первой вставки (попадания) это будут все обновления одного оператора, без каких-либо «если» или «но». Условие «где» для вставки необходимо, иначе она будет вставлять дубликаты, и вы не хотите иметь дело с блокировкой.

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

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

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`

@RamenChef Я не понимаю. Где предложения WHEN MATCHED?

likejudo 31.03.2020 01:45

@likejudo Я этого не писал; Я только переделал. Спросите пользователя, написавшего сообщение.

RamenChef 01.04.2020 02:12

Предполагая, что вы хотите вставить / обновить одну строку, наиболее оптимальный подход - использовать уровень изоляции транзакции REPEATABLE READ SQL Server:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION

    IF (EXISTS (SELECT * FROM myTable WHERE key=@key)
        UPDATE myTable SET ...
        WHERE key=@key
    ELSE
        INSERT INTO myTable (key, ...)
        VALUES (@key, ...)

COMMIT TRANSACTION

Этот уровень изоляции будет предотвращать / блокировать последующие повторяющиеся транзакции чтения от доступа к той же строке (WHERE key=@key), пока текущая транзакция открыта. С другой стороны, операции с другой строкой не будут заблокированы (WHERE key=@key2).

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