我的MSSQL数据库有一个表records
,其中包含XML列data
,如下所示:
<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>
我希望一次更新表格中的所有行,将/record/field/@lang
替换为en-US
此时它是en-GB
的所有元素属性值)。
已尝试类似......
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;
...但它返回数据不变,只有在选择了一行时才有效。如何使这项工作一次更新所有行?
我最终使用了Shnugo建议的XQuery。我稍微概括的查询如下所示:
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]
最顶层节点<record>
的名称似乎需要进行硬编码,因为MSSQL服务器不支持动态元素名称(也不支持属性名称)。它的属性以及除<field>
以外的所有子元素都会自动复制上面的代码。
答案 0 :(得分:1)
是的,遗憾的是,replace value of
语句一次只更新一个节点。因此,在您的情况下,快速和脏的替换将是最容易编写(并且运气好,甚至可能是最快的运行):
update t set [data] = cast(
replace(cast(t.[data] as nvarchar(max)), N' lang="en-GB"', N' lang="en-US"')
as xml)
from dbo.Records t
where t.[data].exist('/record/field[@lang="en-GB"]') = 1;
如果XML架构有所不同,例如无法保证/record
节点始终位于顶层,您可能希望修改过滤器:
where t.[data].exist('//record/field[@lang="en-GB"]') = 1;
另一种方法是使用FLWOR语句,但如果XML结构变化很大并且包含其他不可预测的节点,则不会意外丢失任何内容变得相当困难。这反过来会导致较差的表现。为了使这种方法可行,您的XML模式必须非常稳定。
答案 1 :(得分:1)
由于副作用,我避免将强制转换为字符串类型(但这可能是最简单的方法,尤其是如果XML可能包含其他节点,您未在示例中显示这些节点...)< / p>
我也避免循环。
我的方法是切碎并重新创建XML:
DECLARE @xml XML=
N'<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>';
- 查询将读取所有字段的值,并使用更改的语言重建XML
WITH Shredded AS
(
SELECT fld.value(N'@tag',N'nvarchar(max)') AS tag
,fld.value(N'@occ',N'int') AS occ
,fld.value(N'@lang',N'nvarchar(max)') AS lang
,fld.value(N'(./text())[1]',N'nvarchar(max)') AS content
FROM @xml.nodes(N'/record/field') AS A(fld)
)
SELECT @xml.value(N'(/record/@id)[1]',N'int') AS [@id]
,(
SELECT tag AS [@tag]
,occ AS [@occ]
,CASE WHEN lang='en-GB' THEN 'en_US' ELSE lang END AS [@lang]
,content AS [*]
FROM Shredded
FOR XML PATH('field'),TYPE
) AS [*]
FOR XML PATH(N'record')
结果
<record id="1">
<field tag="DI" occ="1" lang="de-DE">Höhe</field>
<field tag="DI" occ="1" lang="en_US">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_US">width</field>
<field tag="WA">55</field>
<field tag="EE">cm</field>
</record>
答案 2 :(得分:1)
我将此作为第二个答案添加,因为它遵循完全不同的方法。以下代码将使用.query()
和FLWOR
查询来按原样读取XML ,但在内容为{{1}时更改属性lang
}:
en_GB
查询
DECLARE @xml XML=
N'<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>';
结果
SELECT @xml.query
(N'
<record id="{/record/@id}">
{
for $fld in /record/field
return <field>
{
for $attr in $fld/@*
return
if(local-name($attr)="lang" and $attr="en-GB") then attribute lang {"en-US"}
else $attr
}
{$fld/text()}
</field>
}
</record>
')
尝试此操作立即更新完整的表格:
<record id="1">
<field tag="DI" occ="1" lang="de-DE">Höhe</field>
<field tag="DI" occ="1" lang="en-US">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-US">width</field>
<field tag="WA">55</field>
<field tag="EE">cm</field>
</record>
答案 3 :(得分:0)
没有xquery, xpath
的丑陋解决方案......:
DECLARE @xml XML = N'<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>'
SET @xml = REPLACE(CAST(@xml AS nvarchar(max)), '"en-GB"', '"en-US"')
SELECT @xml
并使用modify()
DECLARE @nodeCount int
DECLARE @i int
SET @i = 1
SELECT @nodeCount = @xml.value('count(/record/field/@lang)','int')
WHILE (@i <= @nodeCount)
BEGIN
Set @xml.modify('replace value of (/record/field/@lang)[.="en-GB"][1] with "en-US"')
SET @i = @i + 1
END
SELECT @xml
演示链接:Rextester