sql-server - 用Transact-SQL替换所有记录中的多个XML属性值?

标签 sql-server xml xpath xquery

问题

我的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>以外的所有子元素都将使用上述代码自动复制。

最佳答案

我将其作为第二个答案,因为它采用了完全不同的方法。以下代码将.query()FLWOR查询结合使用,按原样读取XML,但是当内容为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>


更新:这也适用于所有表的行:

尝试以下操作一次更新一个完整的表:

DECLARE @tbl TABLE(ID INT IDENTITY,YourXml XML)
INSERT INTO @tbl VALUES
(
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>'
)
,(
N'<record id="2">
  <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>'
);

UPDATE @tbl SET YourXml=YourXml.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>
');

SELECT * FROM @tbl

关于sql-server - 用Transact-SQL替换所有记录中的多个XML属性值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43546414/

相关文章:

sql - 将具有父/子层次结构的表转换为在单独列中具有父名称的表?

SQL Server - 参数名称中的无效字符

python - 如何制定 Xpath 表达式以从子节点获取属性?

xpath - xpath根据元素部分匹配来拉节点

xslt - 在 xsl :for-each to retrieve nodes which are out of for-each's scope 中使用 current() 的问题

sql - 连接来自两个字段的数据而忽略其中一个字段中的 NULL 值?

sql-server - 查询一个链接的sql server

java - 使用java获取XML的某些值

c++ - 替换而不升压

xml - 搜索特定字符串后获取 xml 中元素值的 shell 脚本