【问题标题】:Replacing Node name in an XML thats stored in a SQL Server database column替换存储在 SQL Server 数据库列中的 XML 中的节点名称
【发布时间】:2017-12-14 14:21:43
【问题描述】:

我想知道如何替换我存储在我的 SQL Server 数据库中的 xml 中的子节点名称

示例 XML

<CompanyStatus>
 <ProductionServers>
  <ProductionServer>
    <Patch>0</Patch>
    <Status>Green</Status>
    <Test_Node>Yes</Test_Node>
 </ProductionServers>
  </ProductionServer>
</CompanyStatus>

如何将其更改为以下内容:

<CompanyStatus>
 <ProductionServers>
  <ProductionServer>
    <Patch>0</Patch>
    <Status>Green</Status>
    <Live_Node>Yes</Live_Node>
 </ProductionServers>
  </ProductionServer>
</CompanyStatus>

本质上唯一的变化是 &lt;Test_Node&gt; 重命名为 &lt;Live_Node&gt; 但值是相同的。

有没有简单的方法来做到这一点?

我的数据库中有大约 1000 条记录

【问题讨论】:

    标签: sql sql-server xml replace


    【解决方案1】:

    使用 XQuery,类似于:

    create function SwitchToLiveNode(@doc xml)
    returns xml
    as
    begin
        declare @val varchar(200) = @doc.value('(/CompanyStatus/ProductionServers/ProductionServer/Test_Node)[1]', 'varchar(200)')
    
        declare @newNode xml = concat('<Live_Node>',@val,'</Live_Node>')
    
        SET @doc.modify('         
        insert sql:variable("@newNode")      
        as last         
        into (/CompanyStatus/ProductionServers/ProductionServer)[1]         
        ')
    
        set @doc.modify('delete /CompanyStatus/ProductionServers/ProductionServer/Test_Node')         
    
        return @doc
    end
    
    go
    
    declare @doc xml = '
    <CompanyStatus>
     <ProductionServers>
      <ProductionServer>
        <Patch>0</Patch>
        <Status>Green</Status>
        <Test_Node>Yes</Test_Node>
     </ProductionServer>
      </ProductionServers>
    </CompanyStatus>'
    
    
    
    select dbo.SwitchToLiveNode(@doc)
    

    【讨论】:

    • 与类似但更一般的场景有关,对此问题有什么建议吗?stackoverflow.com/q/59072683/3276027
    • 我尝试使用与您建议的类似的东西,但实际上它似乎修改了输入 xml 的“本地”副本:我的尝试:select xmlColumn as original, dbo.MyUpdateXmlFunction(xmlColumn) as modified into #tmpTable from myTable select * #tmpTable where original &lt;&gt; modified select * from myTable,在#tmpTable modified 实际上是不同的来自original,但xmlColumn 中的myTable 值没有改变。为了使它工作,我必须使用update myTable set xmlColumn = dbo.MyUpdateXmlFunction(xmlColumn)。你建议的功能是否适用于“本地”(到 fn)副本?
    • 是的,XML 是通过“值语义”传递到函数中的,因此如果您想更改表中的值,请使用 UPDATE。
    【解决方案2】:

    一个快速的选择是通过Replace()

    (更正您的 xml)

    示例

    Update YourTable 
       set XMLCol = replace(cast(XMLCol as nvarchar(max)),'Test_Node>','Live_Node>')
    

    更新的 XML

    <CompanyStatus>
      <ProductionServers>
        <ProductionServer>
          <Patch>0</Patch>
          <Status>Green</Status>
          <Live_Node>Yes</Live_Node>
        </ProductionServer>
      </ProductionServers>
    </CompanyStatus>
    

    编辑 - 如果 Test_Node 具有属性(正如 Dai 正确指出的那样)

    Update YourTable 
       set XMLCol = replace(replace(cast(XMLCol as varchar(max)),'</Test_Node>','</Live_Node>'),'<Test_Node>','<Live_Node>')
    

    【讨论】:

    • 如果Test_Node 有任何属性,您的代码将不起作用。
    【解决方案3】:

    这是我的建议

    • 用属性保存
    • 容忍元素的位置(只要该元素是唯一的)

    检查一下:

    DECLARE @xml XML=
    N'<CompanyStatus>
     <ProductionServers>
      <ProductionServer>
        <Patch>0</Patch>
        <Status>Green</Status>
        <Test_Node a="x" b="y" c="z">Yes</Test_Node>
     </ProductionServer>
      </ProductionServers>
    </CompanyStatus>';
    

    --这将创建 &lt;Test_Node&gt; 及其所有属性(如果有的话),新元素名称为 &lt;Live_Node&gt;

    DECLARE @NewNode XML=
        (
         SELECT @xml.query(N'let $nd:=(//*[local-name()="Test_Node"])[1]
                             return
                             <Live_Node> {$nd/@*}
                             {$nd/text()}
                             </Live_Node>
                            ')
        );
    

    --这会先在原来的后面直接插入"@NewNode",然后会去掉原来的:

    SET @xml.modify(N'insert sql:variable("@NewNode") after (//*[local-name()="Test_Node"])[1]');
    SET @xml.modify(N'delete (//*[local-name()="Test_Node"])[1]');
    
    SELECT @xml;
    

    结果

    <CompanyStatus>
      <ProductionServers>
        <ProductionServer>
          <Patch>0</Patch>
          <Status>Green</Status>
          <Live_Node a="x" b="y" c="z">Yes</Live_Node>
        </ProductionServer>
      </ProductionServers>
    </CompanyStatus>
    

    更新:与使用可更新 CTE 的表格数据相同:

    DECLARE @xmlTable TABLE (YourXml XML);
    INSERT INTO @xmlTable VALUES
    (--Test_Node has got attributes
    N'<CompanyStatus>
     <ProductionServers>
      <ProductionServer>
        <Patch>0</Patch>
        <Status>Green</Status>
        <Test_Node a="x" b="y" c="z">Yes</Test_Node>
     </ProductionServer>
      </ProductionServers>
    </CompanyStatus>'
    )
    ,( --different position, no attributes
    N'<CompanyStatus>
     <ProductionServers>
        <Test_Node>Yes</Test_Node>
      <ProductionServer>
        <Patch>0</Patch>
        <Status>Green</Status>
     </ProductionServer>
      </ProductionServers>
    </CompanyStatus>'
    )
    ,( --No test node at all
    N'<CompanyStatus>
     <ProductionServers>
      <ProductionServer>
        <Patch>0</Patch>
        <Status>Green</Status>
     </ProductionServer>
      </ProductionServers>
    </CompanyStatus>'
    );
    

    --可更新的 CTE 返回原始节点和新节点。这可以一次更新:

    WITH ReadNode AS
    (
        SELECT t.YourXml.query(N'let $nd:=(//*[local-name()="Test_Node"])[1]
                            return
                            <Live_Node> {$nd/@*}
                            {$nd/text()}
                            </Live_Node>
                        ') AS NewNode
             ,t.YourXml AS Original
        FROM @xmlTable AS t
    )
    UPDATE ReadNode SET Original.modify(N'insert sql:column("NewNode") after (//*[local-name()="Test_Node"])[1]');
    
    UPDATE @xmlTable SET YourXml.modify(N'delete (//*[local-name()="Test_Node"])[1]');
    
    SELECT *
    FROM @xmlTable 
    

    【讨论】:

    • 与类似但更一般的场景相关,对这个问题有什么建议吗? stackoverflow.com/q/59072683/3276027
    • 我尝试使用类似于您建议的第一个解决方案的方法,但实际上它似乎修改了输入 xml 的“本地”副本:我的尝试:select xmlColumn as original, dbo.MyUpdateXmlFunction(xmlColumn) as modified into #tmpTable from myTable select * #tmpTable where original &lt;&gt; modified select * from myTable,在#tmpTable modified 中实际上与original 不同,但myTable 中的xmlColumn 值没有改变。为了使它工作,我必须使用update myTable set xmlColumn = dbo.MyUpdateXmlFunction(xmlColumn)。你建议的功能是否适用于“本地”(到 fn)副本?
    【解决方案4】:

    虽然您的问题要求为 SQL 提供解决方案,但我认为最好的解决方案是使用 XML 库正确解析 XML,这在 C# 中是最简单的。

    幸运的是,您可以在 SQL Server 中将其用作 SQL-CLR 存储过程:

    CREATE PROCEDURE ReplaceTestNode(@xml xml) AS EXTERNAL NAME StoredProcedures.ReplaceTestNode
    

    很遗憾,您无法使用 System.Xml 重命名 XML 元素。相反,您创建一个新的替换节点,将其插入与原始节点相同的位置,移动内容(和任何属性),然后删除原始节点。

    public static class StoredProcedures {
    
        [SqlProcedure]
        public static void ReplaceTestNode(SqlXml data, out SqlXml output) {
            XmlDocument doc = new XmlDocument();
    
            using( XmlReader rdr = data.CreateReader() ) {
                doc.Load( rdr );
            }
    
            ReplaceElementName( doc, "Test_Node", "Live_Node" );
    
            using( XmlReader outRdr = new XmlNodeReader( doc ) ) {
                output = new SqlXml( outRdr );
            }
        }
    }
    
    private static void ReplaceElementName(XmlDocument doc, String oldName, String newName) {
    
        XmlNodeList testNodes = doc.GetElementsByTagName( oldName );
        List<XmlElement> testElements = testNodes
            .Where( n => n.NodeType == XmlNodeType.Element )
            .Cast<XmlElement>()
            .ToList();
    
        foreach( XmlElement element in testElements ) {
    
            // 1: Create replacement element and insert in the same location:
            XmlElement replacement = doc.CreateElement( newName );
            element.ParentElement.InsertBefore( replacement, element );
    
            // 2: Move child nodes over
            foreach(XmlNode child in element.ChildNodes) {
                replacement.AppendChild( child );
            }
    
            // 3: Move attributes over
            foreach(XmlAttribute attrib in element.Attributes) {
                replacement.Attributes.Append( attrib );
            }
    
            // 4: Remove
            element.ParentElement.Remove( element );
        }
    }
    

    【讨论】:

      【解决方案5】:

      UPDATE TABLE_NAME 设置 COLUMN_NAME=cast(REPLACE(cast(COLUMN_NAME as VARCHAR(max)),'Test_Node','Live_Node' )作为 XML)

      [注意:使用这个脚本把你的表名和目标列名]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多