如何基于XML参数更新表

时间:2012-05-31 14:57:41

标签: sql-server xml sql-server-2008 tsql

我有一个表,我想根据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参数中指定的所有元素更新字段?

3 个答案:

答案 0 :(得分: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

答案 1 :(得分: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。

的声明之后

答案 2 :(得分: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

将之前与之前创建的表格连接起来(复制未确定替换的orignal值)

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语句中使用它来更新orignal表中的数据。无论如何我意识到代码可以被清理一点,甚至可能简化,但这就是我提出的概念。真正更大的问题是如何存储这些数据。但是我的情况不为我所知,所以我会保留判断力。