【问题标题】:Equivalent of MySQL ON DUPLICATE KEY UPDATE in Sql Server相当于 Sql Server 中的 MySQL ON DUPLICATE KEY UPDATE
【发布时间】:2015-01-20 11:36:23
【问题描述】:

我正在尝试在 Sql Server (2012) 中找到以下 MySql 查询的等效项?

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);

谁能帮忙?

附言。我读过MERGE 查询具有类似的功能,但我发现它的语法非常不同。

【问题讨论】:

标签: mysql sql sql-server


【解决方案1】:

您基本上是在寻找 插入或更新模式,有时也称为 Upsert。

我推荐这个:Insert or Update pattern for Sql Server - Sam Saffron

对于将处理单行的过程,这些事务都可以正常工作:

Sam Saffron 的第一个解决方案(适用于此架构):

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

Sam Saffron 的第二个解决方案(适用于此架构):

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'
      );

The Merge answer provided by Spock 应该做你想做的事。

合并不一定是推荐的。我使用它,但我永远不会向@AaronBertrand 承认这一点。

【讨论】:

    【解决方案2】:

    试试这个... 我添加了 cmets 来尝试解释 SQL Merge 语句中发生的情况。 来源:MSDN : Merge Statement

    Merge 语句与 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】:

      您可以使用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 here

      工作原理

      • 我们首先将尝试插入表中的所有行的列表投影到#temp 表中,并通过键列COL_D 上的LEFT OUTER JOIN 记录其中哪些已经在基础表中检测重复标准。
      • 然后我们需要重复INSERT 语句的实际工作,通过插入那些不在表中的行(因为INSTEAD OF,我们已经从引擎中删除了插入的责任,需要自己做)。
      • 最后,我们使用新“插入”的数据更新匹配行中的所有非键列。

      重点

      • 它在幕后工作,即启用触发器时对表的任何插入都将受到触发器的影响(例如应用程序 ORM、其他存储过程等)。调用者通常不知道INSTEAD OF 触发器已到位。
      • 必须有某种类型的键来检测重复标准(自然或代理)。在这种情况下,我假设COL_D,但它可能是一个复合键。 (密钥但不能是IDENTITY,原因很明显,因为客户端不会插入身份)
      • 触发器适用于单行和多行 INSERTS

      注意

      • standard disclaimers with triggers 应用,INSTEAD OF 触发器更是如此 - 因为这可能会导致 Sql Server 的可观察行为发生令人惊讶的变化,例如这样 - 即使是精心设计的 INSTEAD OF 触发器也会导致数小时的浪费精力和挫败感适用于不知道自己在您的桌子上的开发人员和 DBA。
      • 这将影响到表中的所有插入。不只是你的。

      【讨论】:

      • 我很惊讶这个选项没有更多的赞成票。这是在 MSSQL 中构建 Upsert 的一种创造性方式。我唯一关心的是查询的效率,考虑到行必须复制到临时表。如果插入影响了数十万或数百万行,那么这个触发器的效率有多高,需要先复制每个受影响的行?
      • 几年后,由于性能以及触发器的标准“代码是隐藏原因”,我自己不会遵循自己的建议。基于集合的 MERGE 是进行有效 upserting 的正确方法
      • 虽然我同意关注点分离的概念(代码是隐藏的),但这是一个很好的解决方法示例,用于在需要补偿我们没有的外部应用程序中的错误的情况下控制。
      【解决方案4】:

      存储过程将拯救这一天。

      这里我假设 COL_A 和 COL_B 是唯一列并且是 INT 类型 注意!没有 sql-server 实例 ATM,因此无法保证语法的正确性。 更新!这是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
      

      【讨论】:

      • 您可以通过尝试更新记录来改进此解决方案,然后检查@@ROWCOUNT 是否等于0,如果等于,则可以插入记录。通过这种优化,您可以保存搜索(索引查找或索引扫描)。
      • 同意,这将是更优雅的解决方案
      【解决方案5】:

      在 sql server 中没有 DUPLICATE KEY UPDATE 等价物,但是您可以使用合并和匹配的 sql server 来完成此操作,请看这里: multiple operations using merge

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-14
        • 1970-01-01
        • 2017-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多