T-SQL字符串替换未锁定-最新更新获胜

时间:2019-06-20 16:24:45

标签: sql sql-server tsql sql-update locking

我有以下存储过程,该存储过程旨在遍历字符串列表,其中包含几个prefix.bucketName形式的子字符串。我想遍历每个字符串和每个存储桶名称,并用新的前缀替换旧的前缀,但保持相同的存储桶名称。

举个例子,考虑以下原始字符串:

"(OldPrefix.BucketA)(OldPrefix.BucketB)"

例如,我想获得:

"(NewPrefix.BucketA)(NewPrefix.BucketB)"

实际上得到的是这样的:

"(OldPrefix.BucketA)(NewPrefix.BucketB)"

因此,通常,只有其中一个前缀会更新,并且无法预测哪个是前缀。根据我所做的调查,看来这两个替换实际上都有效,但实际上仅保存了最后一个。似乎SQL应该锁定此列,但是,应同时读取两者,应用替换,然后再写入,将最后一次写入保留在该列中。

这里是查询-为了隐私起见,所有变量名都已更改-为简洁起见,省略了一些错误处理和数据验证代码:

DECLARE @PrefixID INT                   = 1478,
DECLARE @PrefixName_OLD NVARCHAR(50)    = 'OldPrefix',
DECLARE @PrefixName_NEW NVARCHAR(50)    = 'NewPrefix'

BEGIN TRAN 
    -- Code to rename the section itself here not shown for brevity

    UPDATE 
        dbo.Component 
    SET
        AString= REPLACE(AString,'('+@Prefix_OLD+'.'+b.BucketName+')', '('+@PrefixName_NEW+'.'+b.BucketName+')'),
    FROM
        dbo.Component sc
    JOIN
        dbo.ComponentBucketFilterInString  fis
    ON
        sc.ComponentID = fis.ComponentID
    JOIN
        dbo.Buckets b
    ON
        fis.BucketID = b.BucketID   
    WHERE
        b.PrefixID = @PrefixID
COMMIT
RETURN 1

当我使用while循环编写相同的查询时,它会按预期执行:

DECLARE @BucketsToUpdate TABLE 
(
    BucketID INT,
    BucketName VARCHAR(256)
)

INSERT INTO @BucketsToUpdate
SELECT BucketID, BucketName
FROM Buckets WHERE PrefixID = @PrefixID

WHILE EXISTS(SELECT 1 FROM @BucketsToUpdate)
BEGIN
    DECLARE @currentBucketID INT,
            @currentBucketName VARCHAR(256)
    SELECT TOP 1 @currentBucketID = bucketID, @currentBucketName = bucketName FROM @BucketsToUpdate 
    UPDATE 
            dbo.Component 
        SET
            AString = REPLACE(AString,'('+@PrefixName_OLD+'.'+@currentBucketName+')', '('+@PrefixName_NEW+'.'+@currentBucketName+')')
        FROM
            dbo.Component  sc
        JOIN
            dbo.ComponentBucketFilterInString  fis
        ON
            sc.ComponentID = fis.ComponentID
        WHERE fis.BucketID = @currentBucketID
    DELETE FROM @BucketsToUpdate WHERE BucketID = @currentBucketID
END

为什么第一个版本失败?我该如何解决?

2 个答案:

答案 0 :(得分:1)

UPDATE FROM JOIN可能存在多个匹配项时,您遇到的问题是“未定义”行为。

为了使更新成为可能,您应该多次运行它,如第二个代码演示中所建议的那样一次更新一对值。

相关:How is this script updating table when using LEFT JOINs?Let’s deprecate UPDATE FROM!

  

如果SQL Server与联接表中的多行匹配,SQL Server将愉快地一次又一次地更新同一行,而>>仅保留那些更新的最后结果<<。

答案 1 :(得分:0)

不确定为什么要使整个过程变得如此复杂。可能是我不清楚要求。根据我的理解,您希望仅更新表dbo.Component中列“ AString”的前缀部分。当前值例如是-

(OldPrefix.BucketA)(OldPrefix.BucketB)

您要将值更新为-

(NewPrefix.BucketA)(NewPrefix.BucketB)

我是对的吗?如果是,则可以使用以下简单的“更新”脚本来更新所有记录-

DECLARE @PrefixID INT                   = 1478
DECLARE @PrefixName_OLD NVARCHAR(50)    = 'OldPrefix'
DECLARE @PrefixName_NEW NVARCHAR(50)    = 'NewPrefix'

UPDATE  Component 
SET AString= REPLACE(AString,@PrefixName_OLD,@PrefixName_NEW)