简体   繁体   English

T-SQL替换XML节点

[英]T-SQL Replace XML node

I would like to replace an XML node with a new node. 我想用一个新节点替换XML节点。 I am trying to make this dynamic so the replacement node name is a variable 我正在尝试使此动态,以便替换节点名称是一个变量

DECLARE @xmlSource AS XML = '<Root><Transactions><ReplaceMe>This information should be gone</ReplaceMe></Transactions></Root>'
DECLARE @xmlInsert AS XML = '<NewNode>New Information</NewNode>'
DECLARE @NodeName NVARCHAR(500) = 'ReplaceMe'

The resulting XML should look like: 生成的XML应该如下所示:

<Root><Transactions><NewNode>New Information</NewNode></Transactions></Root>

There is no direct approach to replace a complete node with another one. 没有直接的方法可以用另一个替换整个节点。

But you can delete it and insert the new one: 但是您可以删除它并插入新的:

DECLARE @xmlSource AS XML = '<Root><Transactions><ReplaceMe>This information should be gone</ReplaceMe></Transactions></Root>'
DECLARE @xmlInsert AS XML = '<NewNode>New Information</NewNode>'
DECLARE @NodeName NVARCHAR(500) = 'ReplaceMe'

SET @xmlSource.modify('delete /Root/Transactions/*[local-name(.) eq sql:variable("@NodeName")]');

SELECT @xmlSource; --ReplaceMe is gone...

SET @xmlSource.modify('insert sql:variable("@xmlInsert") into (/Root/Transactions)[1]');

SELECT @xmlSource; 

The result 结果

<Root>
  <Transactions>
    <NewNode>New Information</NewNode>
  </Transactions>
</Root>

UPDATE a generic solution 更新通用解决方案

From your comments I understand, that you have no idea about the XML, just the need to replace one node where you know the name with another node... 从您的评论中我了解到,您对XML一无所知,只是需要将知道名称的一个节点替换为另一节点...

This solution is string based (which is super ugly anyway) and has some flaws: 该解决方案基于字符串(无论如何都很难看),并且存在一些缺陷:

  • If there are several nodes with this name, only the first one will be taken 如果有多个使用此名称的节点,则仅采用第一个
  • If the node exists multiple times with the same content, it would be replaced in both places 如果该节点多次存在且具有相同的内容,则将在两个地方都将其替换
  • If your xml contains CDATA -sections they will be transfered into properly escaped normal XML implicitly. 如果您的xml包含CDATA -sections,它们将被隐式转换为正确转义的普通XML。 No semantic loss, but this could break structural validations... 没有语义上的损失,但这可能会破坏结构验证...

This should work even with special characters, as all conversions are from XML to NVARCHAR and back. 即使所有特殊字符都可以使用,因为所有转换都是从XML到NVARCHAR转换。 Escaped characters should stay the same on both sides. 转义的字符在两侧应保持相同。

Otherwise one had to use a recursive approach to get the full path to the node and build my first statement dynamically. 否则,必须使用递归方法来获取节点的完整路径并动态构建我的第一条语句。 This was cleaner but more heavy... 这是更干净,但更重...

DECLARE @xmlSource AS XML = '<Root><Transactions><ReplaceMe>This information should be gone</ReplaceMe></Transactions></Root>'
DECLARE @xmlInsert AS XML = '<NewNode>New Information</NewNode>'
DECLARE @NodeName NVARCHAR(500) = 'ReplaceMe'

SELECT 
CAST( 
REPLACE(CAST(@xmlSource AS NVARCHAR(MAX))
       ,CAST(@xmlSource.query('//*[local-name(.) eq sql:variable("@NodeName")][1]') AS NVARCHAR(MAX))
       ,CAST(@xmlInsert AS NVARCHAR(MAX))
        )
AS XML) 

To replace in place with XML we'll need to insert our new nodes immediately before (or after) the nodes we want to replace. 要用XML替换,我们需要在要替换的节点之前(或之后)立即插入新节点。

Start off by getting the number of nodes we want to replace: 首先获取我们要替换的节点数:

DECLARE @numToReplace int = @xmlSource.value('count(//*[local-name(.) eq sql:variable("@NodeName")])', 'int')

Then iterate through each node and flag the node to be deleted (this lets us replace the nodes with a node of the same name). 然后遍历每个节点并标记要删除的节点(这使我们可以用相同名称的节点替换节点)。

DECLARE @iterator int = @numToReplace
WHILE @iterator > 0
BEGIN
    SET @xmlSource.modify('insert attribute ToDelete {"delete"} into ((//*[local-name(.) eq sql:variable("@NodeName")])[sql:variable("@iterator")])[1]');
    SET @iterator = @iterator - 1
END

nb you need to nest the target query ((*query*)[sql:variable("@numToReplace")])[1] , it doesn't like variables in the last node indexer nb您需要嵌套目标查询((*query*)[sql:variable("@numToReplace")])[1] ,它不喜欢最后一个节点索引器中的变量

Then insert the new node before each old node 然后在每个旧节点之前插入新节点

SET @iterator = @numToReplace
WHILE @iterator > 0
BEGIN
    SET @xmlSource.modify('insert sql:variable("@xmlInsert") before ((//*[local-name(.) eq sql:variable("@NodeName")][@ToDelete="true"])[sql:variable("@iterator")])[1]')
    SET @iterator = @iterator - 1;
END

Then you can just remove all the old nodes 然后,您可以删除所有旧节点

SET @xmlSource.modify('delete (//*[local-name(.) eq sql:variable("@NodeName")][@ToDelete="true"])')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM