1

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 连接并用 new_val 替换 xml 节点值来替换每个Id的值。ABC 应替换为 123,DEF 应替换为 456

4

2 回答 2

2

There is no easy way to achieve this, due to the fact that even now, in 2018, the replace value of XML method still does not support modification of multiple nodes.

The simplest approach is the insert+delete tactic, as in the following example:

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;

Its weakness is that it reconstructs the entire /CIds node, so if you have any additional data in it, like attributes, it might be too cumbersome to recreate. In this case you might have better success with a FLWOR update, but they tend to be quite slow, compared to other options.

Alternatively, you might run a series of atomic updates in a loop. As unpleasant as it may sound, it will actually work, especially if your actual XML is much more complex than the provided example.

于 2018-08-17T01:20:03.447 回答
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是一个基于旧值返回新值的函数。

于 2018-08-18T07:04:45.950 回答