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

我已经写了一个存储过程,如果存在一个logging将做更新,否则将做一个插入。 它看起来像这样:

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

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

这样做的替代方法是做一个select,然后根据返回的行数进行更新或插入。 这是我认为效率低下,因为如果你要更新它会导致2select(第一个显式select调用,第二个隐含在更新的位置)。 如果proc做插入,那么效率就不会有差别。

我的逻辑在这里发声吗? 这是如何将一个插入和更新结合到一个存储过程?

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

UPSERT的重要性 – 来自sqlservercentral.com :

对于上面提到的每个更新,如果我们使用UPSERT而不是EXISTS,我们将从表中删除一个附加的读取。 不幸的是,对于Insert,UPSERT和IF EXISTS方法在表上使用相同数量的读取。 因此,只有在有一个非常有效的理由certificate额外的I / O时,才能检查存在。 优化的方法是确保在数据库上尽可能less地读取数据。

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

编辑 :请查看这个答案和链接的博客文章,了解这种模式的问题,以及如何使其工作安全。

请阅读我的博客上的文章 ,以获得一个可以使用的良好,安全的模式。 有很多的考虑,这个问题上接受的答案是不安全的。

为了快速回答,请尝试以下模式。 它将在SQL 2000及更高版本上正常工作。 SQL 2005为您提供了error handling,打开了其他选项,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 

如果要与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 

这将导致额外的性能成本,但将确保数据的完整性。

如已经build议的那样,应该在可用的地方使用MERGE。

顺便说一下,MERGE是SQL Server 2008中的新function之一。

您不仅需要在事务中运行它,还需要高隔离级别。 事实上,默认的隔离级别是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 

Mayne还添加了@@错误检查和回滚可能是个好主意。

UPSERT的大风扇,确实减less了代码pipe理。 这里是另一种方法:input参数之一是ID,如果ID是NULL或0,你知道它是一个INSERT,否则它是一个更新。 假设应用程序知道是否有一个ID,所以不会在所有情况下工作,但如果你这样做将削减执行一半。

如果你没有在SQL 2008中进行合并,你必须将其更改为:

如果@@ rowcount = 0和@@ error = 0

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

你的逻辑看起来很合理,但是如果你已经传入了一个特定的主键,你可能需要考虑添加一些代码来防止插入。

否则,如果更新不影响任何logging,则总是进行插入操作,那么在“UPSERT”运行之前有人删除logging时会发生什么? 现在您尝试更新的logging不存在,因此它将创build一条logging。 这可能不是你正在寻找的行为。

修改后的Dima Malenkopost:

 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 

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