我编写了一个存储过程,如果存在记录则会进行更新,否则会执行插入操作。它看起来像这样:
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要进行插入,则效率没有差异。
我的逻辑声音在这里吗? 这是如何将插入和更新组合到存储过程中的?
答案 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发送任何数据,如果可能的话,在内部修复它。