在SQL Server上执行INSERT或者UPDATE的解决方案
假设表结构为MyTable(KEY,datafield1,datafield2 ...)。
我经常想更新现有记录,或者如果不存在则插入新记录。
本质上:
IF (key exists) run update command ELSE run insert command
编写此代码的最佳方式是什么?
解决方案
进行选择,如果得到结果,则对其进行更新,否则,将其创建。
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID) UPDATE [Table] SET propertyOne = propOne, property2 . . . ELSE INSERT INTO [Table] (propOne, propTwo . . .)
编辑:
las,即使对我自己不利,我也必须承认这样做的解决方案似乎是更好的选择,因为它们以较少的步骤完成了任务。
进行UPSERT:
UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key IF @@ROWCOUNT = 0 INSERT INTO MyTable (FieldA) VALUES (@FieldA)
http://en.wikipedia.org/wiki/Upsert
不要忘记交易。性能不错,但是简单的(IF EXISTS ..)方法非常危险。
当多个线程尝试执行插入或者更新时,我们可以轻松地进行
得到主键冲突。
@Beau Crawford和@Esteban提供的解决方案显示了总体思路,但容易出错。
为了避免死锁和PK违规,我们可以使用以下方法:
begin tran if exists (select * from table with (updlock,serializable) where key = @key) begin update table set ... where key = @key end else begin insert into table (key, ...) values (@key, ...) end commit tran
或者
begin tran update table with (serializable) set ... where key = @key if @@rowcount = 0 begin insert into table (key, ...) values (@key,..) end commit tran
进行if if ... else ...涉及最少执行两个请求(一个进行检查,一个进行操作)。以下方法只需要一个存在记录的地方,如果需要插入则两个:
DECLARE @RowExists bit SET @RowExists = 0 UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123 IF @RowExists = 0 INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')
我通常会执行其他一些发帖人所说的,首先检查它是否存在,然后再执行正确的路径。执行此操作时应记住的一件事是,对于一个路径或者另一路径,sql缓存的执行计划可能不是最佳的。我相信做到这一点的最佳方法是调用两个不同的存储过程。
FirstSP: If Exists Call SecondSP (UpdateProc) Else Call ThirdSP (InsertProc)
现在,我不再经常听取自己的建议,所以要加些盐。
如果使用ADO.NET,则DataAdapter会处理此问题。
如果我们想自己处理,可以这样:
确保键列上存在主键约束。
然后你:
- 做更新
- 如果更新失败是因为已存在包含键的记录,请执行插入操作。如果更新没有失败,则说明我们已完成。
我们也可以采用另一种方法,即先插入,如果插入失败,则进行更新。通常,第一种方法更好,因为更新比插入要频繁。
在SQL Server 2008中,我们可以使用MERGE语句
如果要一次UPSERT多个记录,则可以使用ANSI SQL:2003 DML语句MERGE。
MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition) WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...] WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])
在SQL Server 2005中查看"模仿MERGE语句"。
MS SQL Server 2008引入了MERGE语句,我认为它是SQL:2003标准的一部分。正如许多人所表明的,处理一行情况不是什么大问题,但是在处理大型数据集时,需要一个游标,并伴随着所有的性能问题。在处理大型数据集时,将非常欢迎MERGE语句。
查看我对之前非常相似的问题的详细解答
@Beau Crawford的方法在SQL 2005及更低版本的SQL中是一种好方法,尽管如果我们授予rep,则应该由第一个人这样做。唯一的问题是,对于插入来说,它仍然是两个IO操作。
MS Sql2008从SQL:2003标准引入了merge
:
merge tablename with(HOLDLOCK) as target using (values ('new value', 'different value')) as source (field1, field2) on target.idfield = 7 when matched then update set field1 = source.field1, field2 = source.field2, ... when not matched then insert ( idfield, field1, field2, ... ) values ( 7, source.field1, source.field2, ... )
现在,它实际上只是一个IO操作,但是代码很糟糕:-(
尽管对此评论还为时已晚,但我想使用MERGE添加一个更完整的示例。
这种Insert + Update语句通常称为" Upsert"语句,可以在SQL Server中使用MERGE来实现。
这里给出一个很好的例子:
http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
上面还解释了锁定和并发方案。
我将引用相同的内容以供参考:
ALTER PROCEDURE dbo.Merge_Foo2 @ID int AS SET NOCOUNT, XACT_ABORT ON; MERGE dbo.Foo2 WITH (HOLDLOCK) AS f USING (SELECT @ID AS ID) AS new_foo ON f.ID = new_foo.ID WHEN MATCHED THEN UPDATE SET f.UpdateSpid = @@SPID, UpdateTime = SYSDATETIME() WHEN NOT MATCHED THEN INSERT ( ID, InsertSpid, InsertTime ) VALUES ( new_foo.ID, @@SPID, SYSDATETIME() ); RETURN @@ERROR;
在每个人都出于直接运行这些存储过程的这些恶名昭著的用户的担心而跳入HOLDLOCK -s之前,让我指出,我们必须通过设计保证新PK-s的唯一性(身份密钥,Oracle中的序列生成器,唯一索引)外部ID,索引覆盖的查询)。这就是问题的阿尔法和欧米茄。如果没有,则宇宙中没有HOLDLOCK可以保存我们;如果确实有,那么我们不需要在UPDLOCK之外的任何内容(或者先使用更新)。
Sproc通常在受严格控制的条件下运行,并且假定调用方是受信任的(中间层)。这意味着,如果一个简单的upsert模式(update + insert或者merge)曾经看到过重复的PK,这意味着中间层或者表设计中存在错误,那么在这种情况下SQL会大吼大叫并拒绝记录是件好事。在这种情况下,放置一个HOLDLOCK等于吃掉异常并获取潜在的错误数据,这不仅会降低性能。
话虽这么说,使用MERGE或者UPDATE然后在服务器上使用INSERT更加容易,并且更容易出错,因为我们不必记住要添加(UPDLOCK)来进行首次选择。另外,如果我们要进行小批量的插入/更新,则需要了解数据,以便确定交易是否合适。它只是不相关记录的集合,因此其他"包封"交易将是有害的。