Эквивалент MySQL ON DUPLICATE KEY UPDATE в Sql Server

Я пытаюсь найти эквивалент следующего запроса MySql в Sql Server (2012)?

INSERT INTO mytable (COL_A, COL_B, COL_C, COL_D)
VALUES ( 'VAL_A','VAL_B', 'VAL_C', 'VAL_D')
ON DUPLICATE KEY UPDATE COL_D= VALUES(COL_D);

Может ли кто-нибудь помочь?

PS. Я прочитал, что запрос MERGE имеет схожую функцию, но я нахожу синтаксис этого совсем другого.

Ответ 1

В основном вы ищете шаблон Вставить или Обновить, иногда называемый Upsert.

Я рекомендую это: Вставить или Обновить шаблон для Sql Server - Сэм Шаффрон

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

Первое решение Сэма Шаффрона (адаптировано для этой схемы):

begin tran
if exists (
  select * 
    from mytable with (updlock,serializable) 
    where col_a = @val_a
      and col_b = @val_b
      and col_c = @val_c
  )
  begin
    update mytable
      set col_d = @val_d
      where col_a = @val_a
        and col_b = @val_b
        and col_c = @val_c;
  end
else
  begin
    insert into mytable (col_a, col_b, col_c, col_d)
      values (@val_a, @val_b, @val_c, @val_d);
  end
commit tran

Второе решение Сэма Шафрана (адаптировано для этой схемы):

begin tran
  update mytable with (serializable)
    set col_d = @val_d
      where col_a = @val_a
        and col_b = @val_b
        and col_c = @val_c;
  if @@rowcount = 0
    begin
        insert into mytable (col_a, col_b, col_c, col_d)
          values (@val_a, @val_b, @val_c, @val_d);
     end
commit tran

Даже при творческом использовании IGNORE_DUP_KEY вам все равно придется использовать блок вставки/обновления или оператор слияния.

update mytable
  set col_d = 'val_d'
  where col_a = 'val_a'
    and col_b = 'val_b'
    and col_c = 'val_c';

insert into mytable (col_a, col_b, col_c, col_d)
  select 'val_a','val_b', 'val_c', 'val_d'
  where not exists (select * 
    from mytable with (serializable) 
    where col_a = 'val_a'
      and col_b = 'val_b'
      and col_c = 'val_c'
      );

Ответ Merge, предоставленный Spock, должен делать то, что вы хотите.

Слияние не обязательно рекомендуется. Я использую его, но я никогда не признаюсь в этом @AaronBertrand.

Ответ 2

Попробуйте это... Я добавил комментарии, чтобы попытаться объяснить, что происходит в инструкции SQL Merge. Источник: MSDN: выражение о слиянии

Заявление о слиянии отличается от инструкции ON DUPLICATE KEY UPDATE, в которой вы можете указать, какие столбцы использовать для слияния.

CREATE TABLE #mytable(COL_A VARCHAR(10), COL_B VARCHAR(10), COL_C VARCHAR(10), COL_D VARCHAR(10))
INSERT INTO #mytable VALUES('1','0.1', '0.2', '0.3'); --<These are the values we'll be updating

SELECT * FROM #mytable --< Starting values (1 row)

    MERGE #mytable AS target --< This is the target we want to merge into
    USING ( --< This is the source of your merge. Can me any select statement
        SELECT '1' AS VAL_A,'1.1' AS VAL_B, '1.2' AS VAL_C, '1.3' AS VAL_D --<These are the values we'll use for the update. (Assuming column COL_A = '1' = Primary Key)
        UNION
        SELECT '2' AS VAL_A,'2.1' AS VAL_B, '2.2' AS VAL_C, '2.3' AS VAL_D) --<These values will be inserted (cause no COL_A = '2' exists)
        AS source (VAL_A, VAL_B, VAL_C, VAL_D) --< Column Names of our virtual "Source" table
    ON (target.COL_A = source.VAL_A) --< This is what we'll use to find a match "JOIN source on Target" using the Primary Key
    WHEN MATCHED THEN --< This is what we'll do WHEN we find a match, in your example, UPDATE COL_D = VALUES(COL_D);
        UPDATE SET
            target.COL_B = source.VAL_B,
            target.COL_C = source.VAL_C,
            target.COL_D = source.VAL_D
    WHEN NOT MATCHED THEN --< This is what we'll do when we didn't find a match
    INSERT (COL_A, COL_B, COL_C, COL_D)
    VALUES (source.VAL_A, source.VAL_B, source.VAL_C, source.VAL_D)
    --OUTPUT deleted.*, $action, inserted.* --< Uncomment this if you want a summary of what was inserted on updated.
    --INTO #Output  --< Uncomment this if you want the results to be stored in another table. NOTE* The table must exists
    ;
SELECT * FROM #mytable --< Ending values (2 row, 1 new, 1 updated)

Надеюсь, что поможет

Ответ 3

Сохраненная процедура сохранит день.

Здесь я предполагаю, что COL_A и COL_B являются уникальными столбцами и являются типом INT NB! У вас нет контейнера экземпляров sql-сервера, поэтому он не может гарантировать правильность синтаксиса. ОБНОВИТЬ! Вот ссылка на SQLFIDDLE

 CREATE TABLE mytable
(
COL_A int UNIQUE,
COL_B int UNIQUE,
COL_C int,
COL_D int,
)

GO

INSERT INTO mytable (COL_A, COL_B, COL_C, COL_D)
VALUES (1,1,1,1),
(2,2,2,2),
(3,3,3,3),
(4,4,4,4);
GO

CREATE PROCEDURE updateDuplicate(@COL_A INT, @COL_B INT, @COL_C INT, @COL_D INT)
AS
BEGIN
    DECLARE @ret INT
    SELECT @ret = COUNT(*) 
    FROM mytable p 
    WHERE p.COL_A = @COL_A 
        AND p.COL_B = @COL_B

     IF (@ret = 0) 
        INSERT INTO mytable (COL_A, COL_B, COL_C, COL_D)
        VALUES ( @COL_A, @COL_B, @COL_C, @COL_D)

     IF (@ret > 0)
        UPDATE mytable SET COL_D = @COL_D WHERE col_A = @COL_A AND COL_B = @COL_B  
END;
GO

Затем вызовите эту процедуру с необходимыми значениями вместо оператора Update

exec updateDuplicate 1, 1, 1, 2
GO
SELECT * from mytable
GO

Ответ 4

Вы можете моделировать почти идентичное поведение с помощью INSTEAD OF TRIGGER:

CREATE TRIGGER tMyTable ON MyTable
INSTEAD OF INSERT
AS
    BEGIN
        SET NOCOUNT ON;

        SELECT i.COL_A, i.COL_B, i.COL_C, i.COL_D, 
            CASE WHEN mt.COL_D IS NULL THEN 0 ELSE 1 END AS KeyExists 
            INTO #tmpMyTable
            FROM INSERTED i
            LEFT JOIN MyTable mt
            ON i.COL_D = mt.COL_D;

        INSERT INTO MyTable(COL_A, COL_B, COL_C, COL_D)
            SELECT COL_A, COL_B, COL_C, COL_D
                FROM #tmpMyTable
                WHERE KeyExists = 0;

        UPDATE mt
            SET mt.COL_A = t.COL_A, mt.COL_B = t.COL_B, mt.COL_C = t.COL_C
            FROM MyTable mt 
                INNER JOIN #tmpMyTable t 
                ON mt.COL_D = t.COL_D AND t.KeyExists = 1;
    END;

SqlFiddle здесь

Как это работает

  • Сначала мы проецируем список всех строк, которые необходимо вставить в таблицу, в таблицу #temp, отметив, какая из этих ARE уже находится в базовой таблице через LEFT OUTER JOIN в столбце (столбцах) ключа COL_D которые определяют критерии дублирования.
  • Затем нам нужно повторить фактическую работу оператора INSERT, вставив те строки, которые еще не находятся в таблице (из-за INSTEAD OF мы удалили ответственность за вставку из движка и сделайте это сами).
  • Наконец, мы обновляем все неявные столбцы в сопоставленных строках с новыми "вставленными" данными.

Яркие точки

  • Он работает под обложками, то есть любая вставка в таблицу при включении триггера будет подчиняться триггеру (например, ORM приложения, другим хранимым процедурам и т.д.). Как правило, вызывающий абонент НЕ НЕОБХОДИМО использовать триггер INSTEAD OF.
  • Для обнаружения дублирующего критерия (естественного или суррогатного) должен быть ключ. Я предположил COL_D в этом случае, но это может быть составной ключ. (Ключ, но не может быть IDENTITY по понятным причинам, так как клиент не будет вставлять идентификатор)
  • Триггер работает как для одиночных, так и для нескольких строк INSERTS

NB

  • стандартные оговорки с триггерами применяются, а тем более с INSTEAD OF триггерами - поскольку это может вызвать неожиданные изменения в наблюдаемом поведении Sql Server, например, даже хорошо продуманные триггеры INSTEAD OF могут вызвать частые потери усилий и разочарования для разработчиков и администраторов баз данных, которые не знают о своем присутствии на вашей таблице.
  • Это повлияет на ВСЕ вставки в таблицу. Не только ваши.