使用基于连接的当前节点值从辅助表更新xml节点值

时间:2018-08-16 22:00:02

标签: sql-server xml join xml.modify

dbo.table_a 包含XML列 foo 。每行在XML列中都有一个XML树。 辅助表 dbo.table_b 包含旧值 old_val 和新值 new_val

XML树看起来像这样

<root>
  <Customer>
    <Name>ACME</Name>
  </Customer>
  <Def>
    <Enabled>true</Enabled>
    <CIds>
      <Id>ABC</Id>
      <Id>DEF</Id>
    </CIds>
  </Def>
</root>

请注意/ root / Def / CIds可以包含零个或几个称为Id的子节点。

dbo.table_b 看起来像这样

+---------+---------+
| old_val | new_val |
+---------+---------+
| ABC     |     123 |
| DEF     |     456 |
+---------+---------+

我需要通过将当前xml节点值与 old_val 上的 dbo.table_b 结合并替换xml来替换每个 Id 的值使用new_val的节点值。 应该用123代替ABC,用456代替DEF

2 个答案:

答案 0 :(得分:2)

要实现这一目标,尚不容易,因为即使在现在,replace value of XML方法仍不支持修改多个节点。

最简单的方法是插入+删除策略,如以下示例所示:

declare @TableA table (
    Id int identity(1,1) primary key,
    XData xml not null
);

declare @TableB table (
    OldValue varchar(50),
    NewValue int
);

insert into @TableA (XData)
select N'<root>
  <Customer>
    <Name>ACME</Name>
  </Customer>
  <Def>
    <Enabled>true</Enabled>
    <CIds>
      <Id>ABC</Id>
      <Id>DEF</Id>
    </CIds>
  </Def>
</root>';

insert into @TableB (OldValue, NewValue)
values
    ('ABC', 123),
    ('DEF', 456);

-- Before
select * from @TableA;

update a set XData.modify('insert sql:column("sq.NewData") after /root[1]/Def[1]/CIds[1]')
from @TableA a
    cross apply (
        select b.NewValue as [Id]
        from @TableB b
        where a.XData.exist('/root/Def/CIds/Id[text() = sql:column("b.OldValue")]') = 1
        for xml path(''), type, root('CIds')
    ) sq(NewData);

update a set XData.modify('delete /root[1]/Def[1]/CIds[1]')
from @TableA a;

-- After
select * from @TableA;

它的弱点在于它会重建整个/CIds节点,因此,如果其中包含任何其他数据(如属性),则重新创建可能太麻烦了。在这种情况下,使用FLWOR更新可能会取得更好的成功,但与其他选项相比,它们的速度往往会很慢。

或者,您可以循环运行一系列原子更新。听起来可能令人不愉快,但实际上它会起作用,尤其是当您的实际XML比所提供的示例复杂得多时。

答案 1 :(得分:0)

我最终进行了循环和更新。尽管@Roger Wolf的代码段起作用了,但我无法使其在我的环境中起作用。

declare @I int
declare @old varchar(50)
declare @new varchar(50)

select @I = max([foo].value('count(/root/Def/CustomerIds/CIds)', 'int'))
FROM dbo.table_a where [foo].exist('(count(/root/Def/CustomerIds/CIds/Id)') = 1

while @I > 0 
begin

select @old = [foo].value('(/count(/root/Def/CustomerIds/CIds/Id)[sql:variable("@I")][1]', 'varchar(50)') 
from dbo.table_a
where [foo].exist('(/count(/root/Def/CustomerIds/CIds/Id)[sql:variable("@I")]') = 1

set @new = dbo.ConvertOldNew(@old)


  update dbo.table_a
  set [foo].modify
    ('replace value of ((/count(/root/Def/CustomerIds/CIds/Id)[sql:variable("@I")]/text())[1]
      with (sql:variable("@new"))')
  where [foo].exist('(/count(/root/Def/CustomerIds/CIds/Id)[sql:variable("@I")]') = 1
  set @I = @I - 1
end

请注意,dbo.ConvertOldNew是一个基于旧值返回新值的函数。