我正在尝试实现您的基本UPSERT功能,但要有所不同:有时我不想实际更新现有行。
本质上,我正在尝试在不同存储库之间同步某些数据,而Upsert函数似乎是解决之道。因此,主要基于Sam Saffron's answer to this question以及其他一些研究和阅读,我想到了这个存储过程:
(注意:我使用的是MS SQL Server 2005,因此不能使用MERGE语句)
CREATE PROCEDURE [dbo].[usp_UpsertItem]
-- Add the parameters for the stored procedure here
@pContentID varchar(30) = null,
@pTitle varchar(255) = null,
@pTeaser varchar(255) = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION
UPDATE dbo.Item WITH (SERIALIZABLE)
SET Title = @pTitle,
Teaser = @pTeaser
WHERE ContentID = @pContentID
IF @@rowcount = 0
INSERT INTO dbo.Item (ContentID, Title, Teaser)
VALUES (@pContentID, @pTitle, @pTeaser)
COMMIT TRANSACTION
END
对于基本的Upsert,我对此很满意,但我希望实际更新取决于另一列的值。可以将其视为“锁定”一行,这样Upsert过程就不会进行进一步的更新。我可以这样更改UPDATE语句:
UPDATE dbo.Item WITH (SERIALIZABLE)
SET Title = @pTitle,
Teaser = @pTeaser
WHERE ContentID = @pContentID
AND RowLocked = false
但是,随后的Insert尝试插入已存在但由于“锁定”而未更新的行时,将因唯一约束冲突(针对ContentID字段)而失败。
那么这是否意味着我不再拥有经典的Upsert,即我每次都必须选择该行以确定是否可以更新或插入该行?我敢肯定是这样,所以我想我真正要问的是帮助正确设置事务隔离级别,以便过程可以安全地执行。
最佳答案
我拍了下面的脚本来证明我几年来使用的这个技巧。如果使用它,则需要对其进行修改以适合您的目的。评论如下:
/*
CREATE TABLE Item
(
Title varchar(255) not null
,Teaser varchar(255) not null
,ContentId varchar(30) not null
,RowLocked bit not null
)
UPDATE item
set RowLocked = 1
where ContentId = 'Test01'
*/
DECLARE
@Check varchar(30)
,@pContentID varchar(30)
,@pTitle varchar(255)
,@pTeaser varchar(255)
set @pContentID = 'Test01'
set @pTitle = 'TestingTitle'
set @pTeaser = 'TestingTeasier'
set @check = null
UPDATE dbo.Item
set
@Check = ContentId
,Title = @pTitle
,Teaser = @pTeaser
where ContentID = @pContentID
and RowLocked = 0
print isnull(@check, '<check is null>')
IF @Check is null
INSERT dbo.Item (ContentID, Title, Teaser, RowLocked)
values (@pContentID, @pTitle, @pTeaser, 0)
select * from Item
这里的技巧是您可以在Update语句中的局部变量中设置值。上面,仅当更新有效(即满足更新条件)时,才设置“标志”值;否则,它不会被更改(此处保留为null),您可以进行检查并进行相应处理。
至于事务并使其可序列化,在建议如何进行操作之前,我想了解有关事务中必须封装的内容的更多信息。
-附录,以下第二条评论的后续行动-----------
Saffron先生的想法是实现此例程的一种彻底而可靠的方法,因为您的主键是在外部定义的并传递到数据库中(即您不使用标识列,对我来说很好,它们经常被过度使用)。
我进行了更多测试(在ContentId列上添加了主键约束,在事务中包装了UPDATE和INSERT,在更新中添加了可序列化的提示),是的,这应该可以完成您想要的一切。失败的更新在索引的该部分上施加了范围锁定,这将阻止任何同时尝试在该列中插入该新值的尝试。当然,如果同时提交了N个请求,则“第一个”将创建该行,第二个,第三个等将立即更新该行-除非您沿该行的某个位置设置了“锁定”。好招!
(请注意,在键列上没有索引的情况下,您将锁定整个表。此外,范围锁可能会将行锁定在新值的“任一侧”上,或者可能不会,可以测试一个。这没关系,因为操作的持续时间应为[?]单位毫秒。)
关于sql - 如何实现条件Upsert存储过程?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/1106717/