Вопрос

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

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

По существу:

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

Какой самый эффективный способ написать это?

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

Решение

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

Решения, предоставленные @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

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

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

@Beau Crawford's это хороший способ в SQL 2005 и ниже, хотя, если вы предоставляете rep, он должен перейти к первый парень, который сделал это.Единственная проблема заключается в том, что для вставок это все еще две операции ввода-вывода.

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, ... )

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

Сделайте ПЕРЕВОРОТ:

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

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

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

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

http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

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

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table 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
  INSERT ...
END
COMMIT TRANSACTION;

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

Другие будут предлагать этот способ:

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

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

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

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

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

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

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.

Хотя уже довольно поздно комментировать это, я хочу добавить более полный пример использования 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;
/*
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

Замените названия таблиц и полей на все, что вам нужно.Позаботьтесь о использование НА состояние.Затем установите соответствующее значение (и тип) для переменных в строке ОБЪЯВЛЕНИЯ.

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

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

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

При выполнении ОБНОВЛЕНИЯ if-no-rows-updated затем ВСТАВЬТЕ маршрут, подумайте о том, чтобы сначала выполнить ВСТАВКУ, чтобы предотвратить состояние гонки (при условии отсутствия промежуточного УДАЛЕНИЯ)

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

Помимо исключения состояния гонки, если в большинстве случаев запись уже будет существовать, то это приведет к сбою ВСТАВКИ, что приведет к потере процессора.

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

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

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

После первой вставки (нажатия) это будут все обновления с одним оператором, никаких "если" или "но".Условие "where" для вставки необходимо, иначе оно будет вставлять дубликаты, а вы не хотите иметь дело с блокировкой.

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

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

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

Sprocs обычно выполняются в очень контролируемых условиях и с предположением о доверенном вызывающем абоненте (средний уровень).Это означает, что если простой шаблон upsert (update + insert или merge) когда-либо увидит дублирующийся PK, это означает ошибку в вашем дизайне среднего уровня или таблицы, и хорошо, что SQL в таком случае выдаст ошибку и отклонит запись.Установка БЛОКИРОВКИ в этом случае равносильна использованию исключений и получению потенциально ошибочных данных, помимо снижения вашей производительности.

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

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

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

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

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

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

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

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

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

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

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

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

Вы можете использовать этот запрос.Работает во всех выпусках SQL Server.Это просто и понятно.Но вам нужно использовать 2 запроса.Вы можете использовать, если не можете использовать СЛИЯНИЕ

    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

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

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

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

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

Тогда ты:

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

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

Выполнение запроса if существует ...ещё...включает выполнение минимум двух запросов (один для проверки, другой для принятия мер).Следующий подход требует только одного, если запись существует, и двух, если требуется вставка:

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)

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

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

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