Решения для вставки или обновления на SQL Server - PullRequest
539 голосов
/ 20 сентября 2008

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

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

По существу:

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

Какой самый лучший способ написать это?

Ответы [ 21 ]

367 голосов
/ 28 октября 2008

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

@ Beau Crawford's - хороший способ в 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, ... )

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

340 голосов
/ 20 сентября 2008

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

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

У UPSERT:

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

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

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

81 голосов
/ 19 января 2014

Многие люди предложат вам использовать 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 перехватывать исключения, которые вы могли бы предотвратить в первую очередь, намного дороже, за исключением редкого сценария, когда происходит почти каждая вставка. Я докажу здесь столько же:

51 голосов
/ 20 сентября 2008
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

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

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

36 голосов
/ 20 сентября 2008

Если вы хотите использовать 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 .

10 голосов
/ 21 января 2010

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

Приветствие.

7 голосов
/ 12 октября 2016

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

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
4 голосов
/ 25 августа 2011

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

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 и выше.

...