Microsoft SQL Server - “如果存在则更新,或插入”的最佳方式

TKo*_*KoL 6 sql sql-server

我一直在四处寻找这个问题的答案,但那里有一些相互矛盾或模棱两可的信息,发现很难找到确定的答案。

我的上下文:我在 node.js 中使用 'mssql' npm 包。我的 SQL 服务器是 Microsoft SQL Server 2014。

我有一条记录,它可能已经存在于表中,也可能不存在——如果存在,我想更新它,否则我想插入它。我不确定最佳 SQL 是什么,或者是否应该在 mssql 中运行某种“事务”。我发现了一些看起来不错的选项,但我不确定其中的任何一个:

选项 1: 如何更新(如果存在或插入)

问题是我什至不确定这是 MSSQL 中的有效语法。不过我确实喜欢它,而且它似乎也支持我喜欢的同时执行多行。

INSERT INTO table (id, user, date, points)
    VALUES (1, 1, '2017-03-03', 25),
           (2, 1, '2017-03-04', 25),
           (3, 2, '2017-03-03', 100),
           (4, 2, '2017-03-04', 150)
    ON DUPLICATE KEY UPDATE points = VALUES(points)
Run Code Online (Sandbox Code Playgroud)

选项 2:

不知道这个有没有问题,只是不确定它是否是最佳的。似乎不支持多个同时行

update test set name='john' where id=3012
IF @@ROWCOUNT=0
   insert into test(name) values('john');
Run Code Online (Sandbox Code Playgroud)

选项 3:合并,https : //dba.stackexchange.com/questions/89696/how-to-insert-or-update-using-single-query

有人说这个有点bug什么的?这显然也支持一次我喜欢的多个。

MERGE dbo.Test WITH (SERIALIZABLE) AS T
USING (VALUES (3012, 'john')) AS U (id, name)
    ON U.id = T.id
WHEN MATCHED THEN 
    UPDATE SET T.name = U.name
WHEN NOT MATCHED THEN
    INSERT (id, name) 
    VALUES (U.id, U.name);
Run Code Online (Sandbox Code Playgroud)

Mil*_*ney 13

如果您的系统是高度并发的,并且性能很重要 - 如果更新比插入更常见,您可以尝试以下模式:

BEGIN TRANSACTION;
 
UPDATE dbo.t WITH (UPDLOCK, SERIALIZABLE) SET val = @val WHERE [key] = @key;
 
IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.t([key], val) VALUES(@key, @val);
END
 
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

参考: https: //sqlperformance.com/2020/09/locking/upsert-anti-pattern

另请阅读: https: //michaeljswart.com/2017/07/sql-server-upsert-patterns-and-antipatterns/

如果插入更常见:

BEGIN TRY     
  INSERT INTO dbo.AccountDetails (Email, Etc) VALUES (@Email, @Etc);       
END TRY     
BEGIN CATCH     
  -- ignore duplicate key errors, throw the rest.
  IF ERROR_NUMBER() IN (2601, 2627) 
    UPDATE dbo.AccountDetails
       SET Etc = @Etc
     WHERE Email = @Email;     
END CATCH
Run Code Online (Sandbox Code Playgroud)

我不会使用合并,虽然大多数错误显然已经修复 - 我们在生产之前遇到了重大问题。

编辑 - -

是的,上面的答案是针对单行的 - 对于多行,你会做这样的事情:尽管锁定背后的想法是相同的

BEGIN TRANSACTION;
 
  UPDATE t WITH (UPDLOCK, SERIALIZABLE) 
    SET val = tvp.val
  FROM dbo.t AS t
  INNER JOIN @tvp AS tvp
    ON t.[key] = tvp.[key];
 
  INSERT dbo.t([key], val)
    SELECT [key], val FROM @tvp AS tvp
    WHERE NOT EXISTS (SELECT 1 FROM dbo.t WHERE [key] = tvp.[key]);
 
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)


Der*_*ğlu 6

他们每个人都有不同的目的,优点和缺点。

选项 1 适用于多行插入/更新。但是它只检查主键约束。

选项 2 适用于小数据集。单个记录插入/更新。它更像是脚本。

选项 3 最适合大型查询。可以说,从一个表中读取并相应地插入/更新到另一个表。您可以定义插入和/或更新要满足的条件。您不仅限于主键/唯一约束。