使用 XQuery 仅查找并替换 xml 值的一部分?
Find and replace just a part of a xml value using XQuery?
我的一个专栏中有一个 XML,看起来像这样:
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path3/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path21/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path15/test123/file.doc</VorlagenHistorie>
</BenutzerEinstellungen>
我想用 another test
替换 all test123
次出现(可以不止一次)更新后路径直接指向 test123。
我知道,如何使用相等运算符检查和替换所有值,我在这个答案中看到了它:
Dynamically replacing the value of a node in XML DML
但是是否有 CONTAINS 运算符,是否可以替换值的 INSIDE,我的意思是只替换值的一部分?
提前致谢!
我通常不会建议基于字符串的方法。但在这种情况下,做这样的事情可能是最简单的
declare @xml XML=
'<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
SELECT CAST(REPLACE(CAST(@xml AS nvarchar(MAX)),'/test123/','/anothertest/') AS xml);
更新
如果此方法适用于全球,您可以尝试这样的方法:
我将 XML 读作派生的 table 并将其写回 XML。在这种情况下,您可以确定,只有 VorlageHistorie
的节点会被触及...
SELECT @xml.value('(/BenutzerEinstellungen/State)[1]','nvarchar(max)') AS [State]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM @xml.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen');
更新 2
试试这个。它将读取所有未按原样 VorlagenHistorie
调用的节点,然后添加具有替换值的 VorlageHistorie
节点。唯一的缺点可能是,如果 VorlagenHistorie
元素之后还有其他节点,则文件的顺序会有所不同。但这不应该真正触及您 XML...
的有效性
declare @xml XML=
'<BenutzerEinstellungen>
<State>Original</State>
<Unknown>Original</Unknown>
<UnknownComplex>
<A>Test</A>
</UnknownComplex>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
SELECT @xml.query('/BenutzerEinstellungen/*[local-name(.)!="VorlagenHistorie"]') AS [node()]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM @xml.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen');
更新 3
使用可更新的 CTE 首先获取值,然后一次性设置它们:
declare @tbl TABLE(ID INT IDENTITY,xmlColumn XML);
INSERT INTO @tbl VALUES
(
'<BenutzerEinstellungen>
<State>Original</State>
<Unknown>Original</Unknown>
<UnknownComplex>
<A>Test</A>
</UnknownComplex>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>')
,('<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>');
WITH NewData AS
(
SELECT ID
,xmlColumn AS OldData
,(
SELECT t.xmlColumn.query('/BenutzerEinstellungen/*[local-name(.)!="VorlagenHistorie"]') AS [node()]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM t.xmlColumn.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen'),TYPE
) AS NewXML
FROM @tbl AS t
)
UPDATE NewData
SET OldData=NewXml;
SELECT * FROM @tbl;
如果@shnugo 的回答不符合您的需求,您可以使用XML/XQuery 方法:
DECLARE @xml xml = '<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
DECLARE @from nvarchar(20) = N'test123';
DECLARE @to nvarchar(20) = N'another test';
DECLARE @newValue nvarchar(100) = REPLACE(CONVERT(nvarchar(100), @xml.query('(/BenutzerEinstellungen/VorlagenHistorie/text()[contains(.,sql:variable("@from"))])[1]')), @from, @to)
SET @xml.modify('
replace value of (/BenutzerEinstellungen/VorlagenHistorie/text()[contains(.,sql:variable("@from"))])[1]
with sql:variable("@newValue")')
SELECT @xml
一个奇怪的解决方案,但效果很好:
DECLARE @xml XML = '
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path5/test123/third.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
DECLARE @Counter int = 1,
@newValue nvarchar(max),
@old nvarchar(max) = N'test123',
@new nvarchar(max) = N'anothertest';
WHILE @Counter <= @xml.value('fn:count(//*//*)','int')
BEGIN
SET @newValue = REPLACE(CONVERT(nvarchar(100), @xml.query('((/*/*)[position()=sql:variable("@Counter")]/text())[1]')), @old, @new)
SET @xml.modify('replace value of ((/*/*)[position()=sql:variable("@Counter")]/text())[1] with sql:variable("@newValue")');
SET @Counter = @Counter + 1;
END
SELECT @xml;
输出:
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/anothertest/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path5/anothertest/third.doc</VorlagenHistorie>
</BenutzerEinstellungen>
gofr1 的答案可能会通过使用更具体的 XPath 表达式得到增强:
DECLARE @Counter int = 1,
@newValue nvarchar(max),
@old nvarchar(max) = N'test123',
@new nvarchar(max) = N'anothertest';
WHILE @Counter <= @xml.value('fn:count(/BenutzerEinstellungen/VorlagenHistorie)','int')
BEGIN
SET @newValue = REPLACE(CONVERT(nvarchar(100), @xml.value('(/BenutzerEinstellungen/VorlagenHistorie)[sql:variable("@Counter")][1]','nvarchar(max)')), @old, @new)
SET @xml.modify('replace value of (/BenutzerEinstellungen/VorlagenHistorie[sql:variable("@Counter")]/text())[1] with sql:variable("@newValue")');
SET @Counter = @Counter + 1;
END
SELECT @xml;
我的一个专栏中有一个 XML,看起来像这样:
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path3/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path21/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path15/test123/file.doc</VorlagenHistorie>
</BenutzerEinstellungen>
我想用 another test
替换 all test123
次出现(可以不止一次)更新后路径直接指向 test123。
我知道,如何使用相等运算符检查和替换所有值,我在这个答案中看到了它: Dynamically replacing the value of a node in XML DML
但是是否有 CONTAINS 运算符,是否可以替换值的 INSIDE,我的意思是只替换值的一部分?
提前致谢!
我通常不会建议基于字符串的方法。但在这种情况下,做这样的事情可能是最简单的
declare @xml XML=
'<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
SELECT CAST(REPLACE(CAST(@xml AS nvarchar(MAX)),'/test123/','/anothertest/') AS xml);
更新
如果此方法适用于全球,您可以尝试这样的方法:
我将 XML 读作派生的 table 并将其写回 XML。在这种情况下,您可以确定,只有 VorlageHistorie
的节点会被触及...
SELECT @xml.value('(/BenutzerEinstellungen/State)[1]','nvarchar(max)') AS [State]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM @xml.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen');
更新 2
试试这个。它将读取所有未按原样 VorlagenHistorie
调用的节点,然后添加具有替换值的 VorlageHistorie
节点。唯一的缺点可能是,如果 VorlagenHistorie
元素之后还有其他节点,则文件的顺序会有所不同。但这不应该真正触及您 XML...
declare @xml XML=
'<BenutzerEinstellungen>
<State>Original</State>
<Unknown>Original</Unknown>
<UnknownComplex>
<A>Test</A>
</UnknownComplex>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
SELECT @xml.query('/BenutzerEinstellungen/*[local-name(.)!="VorlagenHistorie"]') AS [node()]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM @xml.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen');
更新 3
使用可更新的 CTE 首先获取值,然后一次性设置它们:
declare @tbl TABLE(ID INT IDENTITY,xmlColumn XML);
INSERT INTO @tbl VALUES
(
'<BenutzerEinstellungen>
<State>Original</State>
<Unknown>Original</Unknown>
<UnknownComplex>
<A>Test</A>
</UnknownComplex>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>')
,('<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>');
WITH NewData AS
(
SELECT ID
,xmlColumn AS OldData
,(
SELECT t.xmlColumn.query('/BenutzerEinstellungen/*[local-name(.)!="VorlagenHistorie"]') AS [node()]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM t.xmlColumn.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen'),TYPE
) AS NewXML
FROM @tbl AS t
)
UPDATE NewData
SET OldData=NewXml;
SELECT * FROM @tbl;
如果@shnugo 的回答不符合您的需求,您可以使用XML/XQuery 方法:
DECLARE @xml xml = '<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
DECLARE @from nvarchar(20) = N'test123';
DECLARE @to nvarchar(20) = N'another test';
DECLARE @newValue nvarchar(100) = REPLACE(CONVERT(nvarchar(100), @xml.query('(/BenutzerEinstellungen/VorlagenHistorie/text()[contains(.,sql:variable("@from"))])[1]')), @from, @to)
SET @xml.modify('
replace value of (/BenutzerEinstellungen/VorlagenHistorie/text()[contains(.,sql:variable("@from"))])[1]
with sql:variable("@newValue")')
SELECT @xml
一个奇怪的解决方案,但效果很好:
DECLARE @xml XML = '
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path5/test123/third.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
DECLARE @Counter int = 1,
@newValue nvarchar(max),
@old nvarchar(max) = N'test123',
@new nvarchar(max) = N'anothertest';
WHILE @Counter <= @xml.value('fn:count(//*//*)','int')
BEGIN
SET @newValue = REPLACE(CONVERT(nvarchar(100), @xml.query('((/*/*)[position()=sql:variable("@Counter")]/text())[1]')), @old, @new)
SET @xml.modify('replace value of ((/*/*)[position()=sql:variable("@Counter")]/text())[1] with sql:variable("@newValue")');
SET @Counter = @Counter + 1;
END
SELECT @xml;
输出:
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/anothertest/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path5/anothertest/third.doc</VorlagenHistorie>
</BenutzerEinstellungen>
gofr1 的答案可能会通过使用更具体的 XPath 表达式得到增强:
DECLARE @Counter int = 1,
@newValue nvarchar(max),
@old nvarchar(max) = N'test123',
@new nvarchar(max) = N'anothertest';
WHILE @Counter <= @xml.value('fn:count(/BenutzerEinstellungen/VorlagenHistorie)','int')
BEGIN
SET @newValue = REPLACE(CONVERT(nvarchar(100), @xml.value('(/BenutzerEinstellungen/VorlagenHistorie)[sql:variable("@Counter")][1]','nvarchar(max)')), @old, @new)
SET @xml.modify('replace value of (/BenutzerEinstellungen/VorlagenHistorie[sql:variable("@Counter")]/text())[1] with sql:variable("@newValue")');
SET @Counter = @Counter + 1;
END
SELECT @xml;