在SQL Server上插入更新存储过程

时间:2008-08-17 06:48:50

标签: sql sql-server stored-procedures upsert

我编写了一个存储过程,如果存在记录则会进行更新,否则会执行插入操作。它看起来像这样:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

我以这种方式编写它的逻辑是更新将使用where子句执行隐式选择,如果返回0,则插入将发生。

以这种方式执行此操作的替代方法是执行select,然后根据返回的行数执行更新或插入。我认为这是低效的,因为如果要进行更新,将导致2次选择(第一次显式选择调用,第二次隐式更新位置)。如果proc要进行插入,则效率没有差异。

我的逻辑声音在这里吗? 这是如何将插入和更新组合到存储过程中的?

9 个答案:

答案 0 :(得分:59)

你的假设是对的,这是做到这一点的最佳方式,它被称为upsert/merge

Importance of UPSERT - from sqlservercentral.com

  

对于上述案例中的每次更新,我们都会删除一个   如果我们,请从表中另外阅读   使用UPSERT而不是EXISTS。   不幸的是,对于插入,两者都是   UPSERT和IF EXISTS方法使用   在桌子上读取相同的数量。   因此检查是否存在   应该只在有了   非常有理由证明这一点   额外的I / O.优化的方式   做的事情就是确保你   尽可能少阅读   DB。

     

最好的策略是尝试   更新。如果没有行受到影响   更新然后插入。多数情况   情况下,行已经   存在,只有一个I / O.   必需的。

修改: 请查看this answer和链接的博客文章,了解此模式的问题以及如何使其安全运行。

答案 1 :(得分:49)

请阅读post on my blog,了解您可以使用的安全模式。有很多考虑因素,这个问题的公认答案远非安全。

要快速回答,请尝试以下模式。它将在SQL 2000及更高版本上正常工作。 SQL 2005为您提供了错误处理,从而打开了其他选项,SQL 2008为您提供了MERGE命令。

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

答案 2 :(得分:10)

如果要与SQL Server 2000/2005一起使用,原始代码需要包含在事务中,以确保数据在并发方案中保持一致。

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

这会产生额外的性能成本,但会确保数据的完整性。

添加,如已经建议的那样,应该在可用的情况下使用MERGE。

答案 3 :(得分:6)

顺便说一句,MERGE是SQL Server 2008中的新功能之一。

答案 4 :(得分:6)

您不仅需要在事务中运行它,还需要高隔离级别。事实上,默认隔离级别是Read Commited,此代码需要Serializable。

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

也许添加@@错误检查和回滚可能是个好主意。

答案 5 :(得分:5)

如果您未在SQL 2008中进行合并,则必须将其更改为:

如果@@ rowcount = 0且@@ error = 0

否则如果更新由于某种原因失败,那么它将尝试之后插入,因为失败语句上的rowcount为0

答案 6 :(得分:3)

UPSERT的忠实粉丝,真正减少了要管理的代码。这是我这样做的另一种方式:其中一个输入参数是ID,如果ID为NULL或0,你知道它是一个INSERT,否则它是一个更新。假设应用程序知道是否有ID,所以在所有情况下都不会工作,但如果你这样做会将执行减少一半。

答案 7 :(得分:1)

您的逻辑似乎很合理,但如果您传入了特定的主键,则可能需要考虑添加一些代码以防止插入。

否则,如果您在更新不影响任何记录时总是进行插入操作,那么当您在“UPSERT”运行之前删除记录时会发生什么?现在您尝试更新的记录不存在,因此它将创建一个记录。这可能不是你想要的行为。

答案 8 :(得分:1)

修改后的Dima Malenko帖子:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

您可以捕获错误并将记录发送到失败的插入表。
我需要这样做,因为我们正在通过WSDL发送任何数据,如果可能的话,在内部修复它。