Problem
My MSSQL database has a table records with an XML column data, which is used like this:
<record id="1">
<field tag="DI" occ="1" lang="de-DE">Höhe</field>
<field tag="DI" occ="1" lang="en-GB">height</field>
<field tag="WA">173</field>
<field tag="EE">cm</field>
<field tag="DI" occ="2" lang="de-DE">Breite</field>
<field tag="DI" occ="2" lang="en-GB">width</field>
<field tag="WA">55</field>
<field tag="EE">cm</field>
</record>
I want to update all rows in the table at once, replacing /record/field/@lang by en-US where it is en-GB at the moment (all elements with that attribute value).
Already tried something like...
declare @i int;
declare @xml xml;
set @xml = (select top(1) [data] from [my-database].[dbo].[records]);
select @i = @xml.value('count(/record/field[lang="en-GB"])', 'int')
while @i > 0
begin
set @xml.modify('
replace value of
(/record/field[lang="en-GB"]/text())[1]
with "en-US"
')
set @i = @i - 1
end
select @xml;
... but it returns the data unchanged and only works if a single row is selected. How can I make this work and update all rows in one go?
Solution
I ended up using XQuery as suggested by Shnugo. My slightly generalized query looks like this:
UPDATE [my-database].[dbo].[records] SET data = data.query(N'
<record>
{
for $attr in /record/@*
return $attr
}
{
for $fld in /record/*
return
if (local-name($fld) = "field")
then <field>
{
for $attr in $fld/@*
return
if (local-name($attr) = "lang" and $attr = "en-GB")
then attribute lang {"en-US"}
else $attr
}
{$fld/node()}
</field>
else $fld
}
</record>
')
FROM [my-database].[dbo].[records]
WHERE [data].exist('/record/field[@lang="en-GB"]') = 1;
SELECT * FROM [my-database].[dbo].[records]
The name of the top most node <record> needs to be hard-coded it seems, because MSSQL server doesn't support dynamic element names (nor attribute names). Its attributes as well as all child elements other than <field> are copied automatically with above code.