TSQL Xpath修改XML字符串

时间:2015-06-18 20:14:12

标签: sql-server xml tsql stored-procedures xpath

我正在尝试修改XML字符串以用其他东西替换节点的值。最终目标是在列中大量加密帐号,但目前我只是想替换XML字符串中的帐号。

当我执行此操作时,我得到"命令已成功完成"即使我试图返回数据。

DECLARE @xml XML = '<optional><account>155555555</account></optional>'


SET @xml.modify('replace value of (/optional/account/text())[1] with "' + (
              SELECT 'ABC-' + ParamValues.item.value('.', 'varchar(max)')
              FROM @xml.nodes('/optional/account') AS ParamValues(item)
       )+ '"')

SELECT @xml

有关为什么不用ABC-Account Number Here替换XML字符串中的帐号并将其返回的任何想法?

2 个答案:

答案 0 :(得分:3)

要回答您的具体问题,“为什么不替换帐号”,这是因为XML函数参数必须是字符串文字。您正在构造modify XML函数的参数,这会导致错误条件。

通常,当您在XML函数参数中不使用字符串文字时,SQL Server会抛出错误,但在这种情况下,由于nodes()函数调用,它必须混淆。

这是一个简单的示例,其中包含一个字符串文字,正如您在评论中指出的那样:

DECLARE @xml XML = '<optional><account>155555555</account></optional>'
SET @xml.modify('replace value of (/optional/account/text())[1] with "1"');
SELECT @xml;

但是,如果您尝试构造modify XML函数参数,如下例所示,它将失败:

DECLARE @xml XML = '<optional><account>155555555</account></optional>'
SET @xml.modify('replace value of (/optional/account/text())[1] with "' + '1' + '"');
SELECT @xml;

错误是:

  

Msg 8172,Level 16,State 1,Line 2 XML数据的参数1   type方法“modify”必须是字符串文字。

显然,如果你通过抛出nodes()来获得一点点发烧友,它会混淆SQL Server并压制错误条件:

DECLARE @xml XML = '<optional><account>155555555</account></optional>'
SET @xml.modify('replace value of (/optional/account/text())[1] with "' + (SELECT '1' FROM @xml.nodes('/optional/')) + '"');
SELECT @xml;

这不是错误,而是在显示任何数据之前终止并简单地打印:

  

命令已成功完成。

所以你无法真正构造XML函数参数。但是,您仍然可以使用外部信息。 Kiran Hedge showed how使用sql:variable() XQuery extension function

然而,你实际上并没有达到那个长度。您正在使用XML内部的数据,XML处理器可以对其进行本机访问。所以你可以这样做:

DECLARE @newXmlSingleReplacement XML = '<optional><account>155555555</account></optional>';
SET @newXmlSingleReplacement.modify('replace value of ((/optional/account)/text())[1] with fn:concat("ABC-",((/optional/account)/text())[1])');
SELECT @newXmlSingleReplacement;

因此Kiran或者此解决方案适用于您的简化示例。但是你可能有一个更复杂的XML文档。它可能有多个“行”,你想改变它们。

如果您使用具有多个帐号的XML文档尝试上述相同的代码,则只替换第一个数字:

DECLARE @newXmlSingleReplacement XML ='<optional><account>155555555</account></optional><optional><account>255555555</account></optional>';
    SET @newXmlSingleReplacement.modify('replace value of ((/optional/account)/text())[1] with fn:concat("ABC-",((/optional/account)/text())[1])');
    SELECT @newXmlSingleReplacement;

结果:

<optional>
  <account>ABC-155555555</account>
</optional>
<optional>
  <account>255555555</account>
</optional>

您可能认为只需删除索引(1)并影响所有行。不幸的是,根据Microsoft's documentationreplace value of的第一个参数“必须只标识一个节点”。因此,您无法列出所有帐号并对其进行操作。

这个例子:

DECLARE @newXmlSingleReplacement XML ='<optional><account>155555555</account></optional><optional><account>255555555</account></optional>';
SET @newXmlSingleReplacement.modify('replace value of ((/optional/account)/text()) with fn:concat("ABC-",((/optional/account)/text()))');
SELECT @newXmlSingleReplacement;

导致此错误:

  

Msg 2389,Level 16,State 1,Line 2 XQuery [modify()]:'concat()'   需要单例(或空序列),找到类型的操作数   'xdt:untypedAtomic *'

相反,您可以循环遍历所有“行”并每次执行modify()操作。您需要一种方法来跟踪计数器的进度。这是一个例子,使用稍微复杂的XML来证明这个概念。

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>';
DECLARE @newxml XML = @xml, @AccountCount int = 0, @Counter int = 0;
SET @AccountCount = @newxml.value('fn:count(//account)','int');
WHILE @Counter <= @AccountCount
BEGIN
    SET @Counter = @Counter + 1;
    SET @newxml.modify('replace value of ((/optional/account)[position()=sql:variable("@Counter")]/text())[1] with fn:concat("ABC-",((/optional/account)[position()=sql:variable("@Counter")]/text())[1])');
END
SELECT  @newxml; 

结果:

<optional>
  <other>Test123</other>
  <account>ABC-155555555</account>
</optional>
<optional>
  <other>Test321</other>
  <account>ABC-255555555</account>
</optional>

当然,如果可以的话,我们更愿意避免SQL代码中的循环。对集合进行操作的单个语句通常会产生更好的性能。

一种选择是粉碎XML并对其进行改造,同时调整流程中的值。此方法的缺点是您必须知道XML的细节才能重构它。它可能也是一个昂贵且复杂的语句,具体取决于XML文档的复杂性。这是一个例子:

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>';
SELECT
    v.value('(./other)[1]','varchar(500)') AS other,
    'ABC-' + v.value('(./account)[1]','varchar(500)') AS account
FROM        @xml.nodes('/optional') AS T(v)
FOR XML PATH ('optional'), TYPE;

但这并不是改革XML的唯一方法。您可以使用XML系统本身及其FLWOR statement support重建它。例如:

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>';
SELECT @xml.query ('
    for $optional in //optional
    return
        <optional>
            {$optional/other}
            <account>ABC-{$optional/account/text()}</account>
        </optional>

');

但同样,这需要了解并手动重新创建XML的结构。有办法避免这种情况。下一个示例需要对现有XML的最少了解。它实际上循环在帐户节点级别的节点上,只有在它们被命名为“account”时才替换它们。

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>';
SELECT @xml.query ('
    for $optional in //optional
    return
        <optional>
            {
                for $subnode in $optional/*
                    return
                        if (fn:local-name($subnode) = "account")
                        then    
                            <account>ABC-{$subnode/text()}</account>
                        else
                            $subnode
            }
        </optional>
');

基于对这些非常小的示例XML文档进行SET STATISTICS TIME ON;的粗略测试,看起来nodes()粉碎和重建的速度稍快一些。它还具有最简单,成本最低的查询计划。

答案 1 :(得分:0)

您无法将该值替换为另一个select语句。您只能将sql变量用于动态值,如下所示

DECLARE @xml XML = '<optional><account>155555555</account></optional>';
DECLARE @val VARCHAR(MAX)
SELECT  @val ='ABC-' + @xml.value('(/optional/account/node())[1]', 'nvarchar(max)')
SET @xml.modify('replace value of (/optional/account/text())[1] with sql:variable("@val")');
SELECT  @xml;