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

T-SQL String Replace is Not Locking - Last Update Wins

我有以下存储过程,它旨在循环访问一个字符串列表,其中包含多个 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

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

不确定为什么要使整个过程如此复杂。可能是我没有清楚地理解这个要求。据我了解,您希望仅更新 table 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)   

您遇到的问题是 "undefined" 当 UPDATE FROM JOIN 有多个匹配项时的行为。

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

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

SQL Server will happily update the same row over and over again if it matches more than one row in the joined table, >>with only the result of the last of those updates sticking<<.