2

我有一个表,我想根据 XML 参数中的值更新它的一个 varchar 字段。

我有下表:

ID  Constraint_Value
1   (OldVal_1) (OldVal_2)
2   (OldVal_2) (OldVal_1)

我想使用以下 XML 来更新该Constraint_Value字段:

<qaUpdates>
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate>
</qaUpdates>

更新后,我的目标是:

ID    Constraint_Value
1     (NewVal_1) (NewVal_2)
2     (NewVal_2) (NewVal_1)

以下 SQL 说明了我的问题(无需任何设置即可在 SQL Management Studio 中运行):

IF OBJECT_ID('tempdb..#tmpConstraint') IS NOT NULL DROP TABLE #tmpConstraint
GO 

CREATE TABLE tempdb..#tmpConstraint ( constraint_id INT PRIMARY KEY, constraint_value varchar(256) )
GO

insert into #tmpConstraint
values (1, '(OldVal_1) (OldVal_2)')

insert into #tmpConstraint
values (2, '(OldVal_2) (OldVal_1)')

select * from #tmpConstraint

declare @myXML XML
set @myXML = N'<qaUpdates>
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate>
</qaUpdates>'

update c
set constraint_value = REPLACE(constraint_value, Child.value('(old)[1]', 'varchar(50)'), Child.value('(new)[1]', 'varchar(50)'))
from #tmpConstraint c
cross join @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 

select * from #tmpConstraint

这给出了结果:

(Before)
1   (OldVal_1) (OldVal_2)
2   (OldVal_2) (OldVal_1)

(After)
1   (NewVal_1) (OldVal_2)
2   (OldVal_2) (NewVal_1)

如您所见,刚刚OldVal_1更新。OldVal_2一直保持不变。

如何使用 xml 参数中指定的所有元素更新字段?

4

3 回答 3

2

使用递归 cte 可以让我得到你正在寻找的结果。如下图所示。但至少它不是游标/while循环;)

declare @tmpConstraint table (ID int , Constraint_Value varchar(256))
insert into @tmpConstraint values 
(1, '(OldVal_1) (OldVal_2)'),
(2, '(OldVal_2) (OldVal_1)')

declare @myXML XML
set @myXML = N'<qaUpdates>
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate>
</qaUpdates>'

declare @xmlData table (oldValue varchar(256), newValue varchar(256))
insert into @xmlData 
select 
    oldValue = Child.value('(old)[1]', 'varchar(50)'), 
    newValue = Child.value('(new)[1]', 'varchar(50)')
from @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 

以上只是为以下设置。

;with cte (ID, Constraint_Value, CLevel)
as
(
    select c.ID, c.Constraint_Value, 1
    from @tmpConstraint c

    union all

    select p.ID, cast(replace(p.Constraint_Value, x.oldValue, x.newValue) as varchar(256)), p.CLevel + 1
    from cte p
    join @xmlData x on p.Constraint_Value like '%' + x.oldValue + '%'
)
update c
set c.Constraint_Value = t.Constraint_Value
from @tmpConstraint c
join (
    select 
        *,
        rn = row_number() over (partition by ID order by CLevel desc)
    from cte
) t on t.ID = c.ID and rn = 1

select * from @tmpConstraint
于 2012-05-31T19:22:29.477 回答
1

我认为这里的问题与 XML 无关。这是一个单一的 UPDATE 只会更新每行一次,而不管存在多少连接行。我认为您可以添加 WHERE 子句和 WHILE 循环来获取所有替换:

WHILE @@ROWCOUNT>0
BEGIN
  update c 
  set constraint_value = REPLACE(constraint_value, Child.value('(old)[1]', 'varchar(50)'), Child.value('(new)[1]', 'varchar(50)')) 
  from #tmpConstraint c 
  cross join @myXML.nodes('/qaUpdates/qaUpdate') as N(Child)  
  WHERE constraint_value LIKE '%' + Child.value('(old)[1]', 'varchar(50)') + '%'
END

只要确保这遵循设置@@RowCount 的语句。

于 2012-05-31T19:23:53.023 回答
0

我意识到这已经得到了回答,但我很想知道是否有办法在不使用 cte 的情况下做到这一点。无论如何,更大的问题实际上是您将 2 条数据存储在同一列/行中。再加上您无法在单个更新语句中两次更新同一行的事实导致了您的问题。无论如何,我的方法是这样的(我提前为复杂性道歉):

DECLARE @tmpConstraint TABLE (
    constraint_id INT PRIMARY KEY
    ,constraint_value VARCHAR(256)
    )

INSERT INTO @tmpConstraint
VALUES (
    1
    ,'(OldVal_1) (OldVal_2)'
    )

INSERT INTO @tmpConstraint
VALUES (
    2
    ,'(OldVal_2) (OldVal_1)'
    )

INSERT INTO @tmpConstraint
VALUES (
    3
    ,'(OldVal_3) (OldVal_21) (OldVal_1)'
    )

DECLARE @myXML XML

SET @myXML = N'<qaUpdates>     <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>     <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate> </qaUpdates>'

SELECT *
FROM @tmpConstraint

UPDATE C
SET constraint_value = c.New_Val
FROM (
    SELECT Constraint_ID UpdID
        ,Constraint_value
        ,STUFF((
                SELECT (' ' + New_value)
                FROM (
                    --Converts XML into a Table effectively splitting the string
                    SELECT constraint_id
                        ,t.value('.', 'varchar(200)') Current_value
                        ,Coalesce(Nullif('(' + new + ')', '()'), t.value('.', 'varchar(200)')) New_Value
                    FROM
                        --Converts single column into an xml document to split rows. Uses a blank space as the identifer of rows
                        (
                        SELECT constraint_id
                            ,convert(XML, ('<R>' + replace(constraint_value, ' ', '</R><R>')) + '</R>') xmldoc
                        FROM @tmpConstraint
                        ) AS a
                    CROSS APPLY a.xmldoc.nodes('./R') AS b(t)
                    --Join to table containing proposed changes based on value to change            
                    LEFT JOIN (
                        SELECT Child.value('./old[1]', 'varchar(100)') old
                            ,Child.value('./new[1]', 'varchar(100)') new
                        FROM @myXML.nodes('/qaUpdates/qaUpdate') AS N(Child
                        )
                    ) q2 ON '(' + old + ')' = t.value('.', 'varchar(200)')
                ) Modified WHERE Modified.constraint_id = base.constraint_id FOR XML path(''))
        ,1,1,'') New_Val
FROM @tmpConstraint Base ) c

SELECT *
FROM @tmpConstraint

它看起来比现在更混乱,如果你有一些 UDF 在那里可以清理。但基本上我将你的多值列分成多行。转动这个

1   (OldVal_1) (OldVal_2)
2   (OldVal_2) (OldVal_1)
3   (OldVal_3) (OldVal_21) (OldVal_1)

进入这个

1   (OldVal_1)
1   (OldVal_2)
2   (OldVal_2)
2   (OldVal_1)
3   (OldVal_3)
3   (OldVal_21)
3   (OldVal_1)

我对 xml 文件做同样的事情。把它变成这样的关键数据对集

OldVal_1    NewVal_1
OldVal_2    NewVal_2

将它与之前创建的表连接起来以获得这个(复制没有确定替换的原始值)

1   (OldVal_1)  (NewVal_1)
1   (OldVal_2)  (NewVal_2)
2   (OldVal_2)  (NewVal_2)
2   (OldVal_1)  (NewVal_1)
3   (OldVal_3)  (OldVal_3)
3   (OldVal_21) (OldVal_21)
3   (OldVal_1)  (NewVal_1)

将分隔的行重新组合成一个字符串,再次按约束 id 分组,如下所示:

1   (OldVal_1) (OldVal_2)       (NewVal_1) (NewVal_2)
2   (OldVal_2) (OldVal_1)       (NewVal_2) (NewVal_1)
3   (OldVal_3) (OldVal_21) (OldVal_1)   (OldVal_3) (OldVal_21) (NewVal_1)

然后在我的 from 语句中使用它来更新原始表中的数据。无论如何,我意识到代码可以稍微清理一下,甚至可能简化,但这就是我想出的概念。真正更大的问题是您如何存储这些数据。但我不知道你的情况,所以我保留判断。

于 2012-06-01T18:09:34.847 回答