修改 XML:将属性转换为元素

Woj*_*teq 11 xml sql-server xquery

我有一XML列包含具有类似结构的数据:

<Root>
    <Elements>
        <Element Code="1" Value="aaa"></Element>
        <Element Code="2" Value="bbb"></Element>
        <Element Code="3" Value="ccc"></Element>
    </Elements>
</Root>
Run Code Online (Sandbox Code Playgroud)

如何使用 SQL Server 修改数据以将每个Value属性更改为元素?

<Root>
    <Elements>
        <Element Code="1">
            <Value>aaa</Value>
        </Element>
        <Element Code="2">
            <Value>bbb</Value>
        </Element>
        <Element Code="3">
            <Value>ccc</Value>
        </Element>
    </Elements>
</Root>
Run Code Online (Sandbox Code Playgroud)

更新:

我的 XML 看起来更像这样:

<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="bbb" ExtraData="extra" />
        <Element Code="3" Value="ccc" ExtraData="extra" />
        <Element Code="4" Value="" ExtraData="extra" />
        <Element Code="5" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>
Run Code Online (Sandbox Code Playgroud)

我只想移动Value属性并保留所有其他属性和元素。

Mik*_*son 13

您可以分解 XML 并使用 XQuery 再次重建它。

declare @X xml = '
<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="" ExtraData="extra" />
        <Element Code="3" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>';

select @X.query('
  (: Create element Root :)
  element Root 
    {
      (: Add all attributes from Root to Root :)
      /Root/@*, 
      (: create element Elements under Root :)
      element Elements 
        {
          (: For each Element element in /Root/Elements :)
          for $e in /Root/Elements/Element
          return 
            (: Add element Element :)
            element Element 
              {
                (: Add all attributes except Value to Element :)
                $e/@*[local-name() != "Value"], 

                (: Check if Attribute Value exist :)
                if (data($e/@Value) != "")
                then
                  (: Create a Value element under Element :)
                  element Value 
                  {
                    (: Add attribute Value as data to the element Element :)
                    data($e/@Value)
                  }
                else () (: Empty element :)
              } 
          },
      (: Add all childelements to Root except the Elements element :)
      /Root/*[local-name() != "Elements"]
    }');
Run Code Online (Sandbox Code Playgroud)

结果:

<Root attr1="val1" attr2="val2">
  <Elements>
    <Element Code="1" ExtraData="extra">
      <Value>aaa</Value>
    </Element>
    <Element Code="2" ExtraData="extra" />
    <Element Code="3" ExtraData="extra" />
  </Elements>
  <ExtraData>
    <!-- Some XML is here -->
  </ExtraData>
</Root>
Run Code Online (Sandbox Code Playgroud)

如果Elements不是Root查询下的第一个元素需要修改为添加Elements第一个之前的所有元素和之后的所有元素Elements


wBo*_*Bob 5

您还可以使用 XML 数据类型的方法(例如modify)和一些 XQuery 来修改 xml,例如

DECLARE @x XML = '<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="bbb" ExtraData="extra" />
        <Element Code="3" Value="ccc" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>'


SELECT 'before' s, DATALENGTH(@x) dl, @x x

-- Add 'Value' element to each Element which doesn't already have one
DECLARE @i INT = 0

WHILE @x.exist('Root/Elements/Element[not(Value)]') = 1
BEGIN

    SET @x.modify( 'insert element Value {data(Root/Elements/Element[not(Value)]/@Value)[1]} into (Root/Elements/Element[not(Value)])[1]' )

    SET @i += 1

    IF @i > 99 BEGIN RAISERROR( 'Too many loops...', 16, 1 ) BREAK END

END

-- Now delete all Value attributes
SET @x.modify('delete Root/Elements/Element/@Value' )

SELECT 'after' s, DATALENGTH(@x) dl, @x x
Run Code Online (Sandbox Code Playgroud)

这种方法往往不能很好地扩展大型 XML 片段,但可能比批量替换 XML 更适合您。

如果您的 XML 存储在表中,您也可以轻松地调整此方法。再次根据经验,我不建议对百万行表运行单个更新。如果您的表很大,请考虑通过它运行游标或以其他方式批量更新。这是技术:

DECLARE @t TABLE ( rowId INT IDENTITY PRIMARY KEY, yourXML XML )

INSERT INTO @t ( yourXML )
SELECT '<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="1" Value="aaa" ExtraData="extra" />
        <Element Code="2" Value="bbb" ExtraData="extra" />
        <Element Code="3" Value="ccc" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>'

INSERT INTO @t ( yourXML )
SELECT '<Root attr1="val1" attr2="val2">
    <Elements>
        <Element Code="21" Value="uuu" ExtraData="extra" />
        <Element Code="22" Value="vvv" ExtraData="extra" />
        <Element Code="23" Value="www" ExtraData="extra" />
        <Element Code="24" Value="xxx" ExtraData="extra" />
        <Element Code="25" Value="yyy" ExtraData="extra" />
        <Element Code="26" Value="zzz" ExtraData="extra" />
    </Elements>
    <ExtraData>
       <!-- Some XML is here -->
    </ExtraData>
</Root>'


SELECT 'before' s, DATALENGTH(yourXML) dl, yourXML
FROM @t 

-- Add 'Value' element to each Element which doesn't already have one
DECLARE @i INT = 0

WHILE EXISTS ( SELECT * FROM @t WHERE yourXML.exist('Root/Elements/Element[not(Value)]') = 1 )
BEGIN

    UPDATE @t
    SET yourXML.modify( 'insert element Value {data(Root/Elements/Element[not(Value)]/@Value)[1]} into (Root/Elements/Element[not(Value)])[1]' )

    SET @i += 1

    IF @i > 99 BEGIN RAISERROR( 'Too many loops...', 16, 1 ) BREAK END

END

-- Now delete all Value attributes
UPDATE @t
SET yourXML.modify('delete Root/Elements/Element/@Value' )

SELECT 'after' s, DATALENGTH(yourXML) dl, yourXML
FROM @t 
Run Code Online (Sandbox Code Playgroud)