7

Решения для INSERT OR UPDATE в SQL Server

3

Проблема: Обновление или вставка записи в таблице

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

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

Основная логика выглядит так:

IF (ключ существует)
  выполнить команду обновления
ELSE
  выполнить команду вставки

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

5 ответ(ов)

4

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

Когда несколько потоков пытаются выполнить операцию "вставить или обновить" (Insert-or-update), вы легко можете столкнуться с нарушением ограничения первичного ключа (PK violation).

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

Чтобы избежать взаимных блокировок (deadlocks) и нарушений первичного ключа, вы можете использовать что-то вроде следующего:

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

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

2

Чтобы выполнить операцию UPSERT в SQL, вы можете использовать следующий подход, который состоит из двух частей: сначала выполняем UPDATE, а затем проверяем, была ли обновлена хотя бы одна строка. Если обновленных строк нет, выполняем INSERT. Пример кода:

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

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

В этом коде:

  1. Сначала мы обновляем значение FieldA для строки, где Key соответствует заданному параметру @Key.
  2. Затем с помощью IF @@ROWCOUNT = 0 проверяем, было ли обновлено хотя бы одно значение. Если @@ROWCOUNT равно 0, значит, строка с указанным Key неExists, и мы выполняем INSERT, чтобы добавить новую запись.

Этот метод гарантирует, что если строка уже существует, она будет обновлена, а если нет — добавится новая строка.

Также стоит отметить, что в некоторых СУБД могут существовать более элегантные или оптимизированные способы выполнения UPSERT, такие как использование команды MERGE. Обязательно ознакомьтесь с документацией к вашей конкретной СУБД для более эффективных решений.

0

Ваш запрос на SQL можно перевести и переформулировать так:

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

Однако, как вы правильно заметили в своем комментарии (Edit), решения, которые избегают использования SELECT в этом контексте, действительно могут оказаться более эффективными, так как они выполняют задачу с меньшим количеством шагов.

Для более эффективного подхода вы можете рассмотреть использование конструкции MERGE, если ваша СУБД это поддерживает. Например:

MERGE INTO [Table] AS target
USING (SELECT rowID AS ID, propOne, propTwo FROM dual) AS source
ON target.ID = source.ID
WHEN MATCHED THEN
    UPDATE SET propertyOne = source.propOne, propertyTwo = source.propTwo, ...
WHEN NOT MATCHED THEN
    INSERT (propertyOne, propertyTwo, ...) VALUES (source.propOne, source.propTwo, ...);

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

0

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

Такие операции вставки и обновления обычно называют "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, обновляя запись, если совпадение найдено, или вставляя новую запись, если совпадений нет. Обратите внимание на использование HOLDLOCK для предотвращения конфликтов в многопользовательской среде.

0

Вы можете использовать следующий код для выполнения операции MERGE в SQL Server, изменяя имена таблиц и полей согласно вашим требованиям:

/*
CREATE TABLE MyTable (
   id                   INT IDENTITY(0,1)    NOT NULL,
   field1               INT                  NOT NULL,
   field2               INT                  NOT NULL,
   isActive             BIT                  NULL,
   CONSTRAINT PK_MyTable PRIMARY KEY (id)
)
GO
--*/

DECLARE @field1 INT = 100, @field2 INT = 200, @isActive BIT = 1;

MERGE dbo.MyTable WITH (HOLDLOCK) AS target
-- задаем источник данных с одной строкой
USING (VALUES (@field1, @field2, @isActive))
    AS source (field1, field2, isActive)
    -- здесь условие соединения
    ON target.field1 = source.field1 AND target.field2 = source.field2
WHEN MATCHED THEN
    UPDATE
    -- здесь укажите списки полей для обновления
    SET target.isActive = source.isActive
WHEN NOT MATCHED THEN
    -- вставляем новую строку из источника
    INSERT (field1, field2, isActive)
    VALUES (source.field1, source.field2, source.isActive);
GO

Замените MyTable, field1, field2, и isActive на актуальные имена таблиц и полей в вашей базе данных. Убедитесь, что условие в секции ON правильно отражает логику сопоставления ваших данных. Также задайте корректные значения и типы данных для переменных в секции DECLARE.

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