表 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
答案 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
是一个基于旧值返回新值的函数。