TSQL Xpath 修改 XML 字符串
TSQL Xpath Modify XML string
我正在尝试修改 XML 字符串以用其他内容替换节点的值。最终目标是在一列中大量加密帐号,但目前,我只是想替换 XML 字符串中的帐号。
当我执行此操作时,即使我正在尝试 return 数据,我也会得到 "Command completed successfully"。
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
关于为什么不将 XML 字符串中的帐号替换为 ABC-Account Number Here
并 return 的任何想法?
您不能用另一个 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;
要回答您的具体问题,"why isn't it replacing the account number",这是因为 XML 函数参数必须是字符串文字。您正在构造 modify
XML 函数的参数,这会导致错误情况。
通常,SQL 当您在 XML 函数参数中没有使用字符串文字时,服务器会抛出错误,但在这种情况下,由于 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 The argument 1 of the XML data
type method "modify" must be a string literal.
显然,如果您通过将 nodes()
放在那里变得更有趣一点,它会混淆 SQL 服务器并消除错误条件:
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;
这不是错误,而是在显示任何数据之前终止并简单地打印:
Command(s) completed successfully.
所以不能真正构造XML函数参数。但是,您仍然可以使用外部信息。基兰对冲 by using the 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 文档。它可能有多个 "rows" 您想更改。
如果您尝试使用上面的相同代码使用具有多个帐号的 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>
您可能认为您可以简单地删除索引 () and affect all of the rows. Unfortunately, according to Microsoft's documentation,replace value of
"must identify only a single node" 的第一个参数。因此您无法获取所有帐号的列表并对其进行操作。
这个例子:
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()'
requires a singleton (or empty sequence), found operand of type
'xdt:untypedAtomic *'
因此,您可以遍历所有 "rows" 并每次执行 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>
');
根据 SET STATISTICS TIME ON;
在这些非常小的示例 XML 文档上进行的粗略测试,nodes()
粉碎和重建似乎稍快一些。它还具有最简单、成本最低的查询计划,而且优势明显。
我正在尝试修改 XML 字符串以用其他内容替换节点的值。最终目标是在一列中大量加密帐号,但目前,我只是想替换 XML 字符串中的帐号。
当我执行此操作时,即使我正在尝试 return 数据,我也会得到 "Command completed successfully"。
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
关于为什么不将 XML 字符串中的帐号替换为 ABC-Account Number Here
并 return 的任何想法?
您不能用另一个 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;
要回答您的具体问题,"why isn't it replacing the account number",这是因为 XML 函数参数必须是字符串文字。您正在构造 modify
XML 函数的参数,这会导致错误情况。
通常,SQL 当您在 XML 函数参数中没有使用字符串文字时,服务器会抛出错误,但在这种情况下,由于 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 The argument 1 of the XML data type method "modify" must be a string literal.
显然,如果您通过将 nodes()
放在那里变得更有趣一点,它会混淆 SQL 服务器并消除错误条件:
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;
这不是错误,而是在显示任何数据之前终止并简单地打印:
Command(s) completed successfully.
所以不能真正构造XML函数参数。但是,您仍然可以使用外部信息。基兰对冲 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 文档。它可能有多个 "rows" 您想更改。
如果您尝试使用上面的相同代码使用具有多个帐号的 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>
您可能认为您可以简单地删除索引 (replace value of
"must identify only a single node" 的第一个参数。因此您无法获取所有帐号的列表并对其进行操作。
这个例子:
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()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
因此,您可以遍历所有 "rows" 并每次执行 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>
');
根据 SET STATISTICS TIME ON;
在这些非常小的示例 XML 文档上进行的粗略测试,nodes()
粉碎和重建似乎稍快一些。它还具有最简单、成本最低的查询计划,而且优势明显。