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.