T-SQL XML : 如果有多个同名节点,如何更新一个节点中的值?
T-SQL XML : how to update value in one node if there are more nodes with identical names?
我刚刚开始学习如何使用 XML 数据格式,但我已经被困在更新一些数据上了。我真的非常感谢对这个问题的一些帮助,因为我完全不知道如何处理这样的问题。
生成一些示例数据的代码:
IF OBJECT_iD('tempdb..#beforeXML') is NOT NULL
DROP TABLE #beforeXML
CREATE TABLE #beforeXML
(
ID int NOT NULL,
SomeXMLData XML NOT NULL
)
INSERT INTO #beforeXML (ID, SomeXMLData)
VALUES
(1, '<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 4</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>'),
(2, '<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 4, 5</Value></Parameter></Parameters>'),
(3, '<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 6</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>'),
(4, '<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>'),
(5, '<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>')
SELECT * FROM #beforeXML
困难的部分来了...
我需要更新“Value”节点的值,其中“Key”节点的值在同一个“Parameter”节点中为“ABC”。
如您所见,我的 xml 中有几个“参数”节点,这些节点没有特定的顺序或属性,我可以用来区分它们并确定我应该更新哪些节点。有些行没有这样的节点,有些行在“值”节点中已经有数字 3 or/and 5,所以我只需要添加一个或两个 (3 or/and 5) 不见了。
我想得到的结果:
IF OBJECT_iD('tempdb..#afterXML') IS NOT NULL
DROP TABLE #afterXML
CREATE TABLE #afterXML
(
ID int NOT NULL,
SomeXMLData XML NOT NULL
)
INSERT INTO #afterXML (ID, SomeXMLData)
VALUES
-- added both 3, 5
(1, '<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 3, 4, 5</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>'),
-- added only 3
(2, '<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter>
<Parameter><Key>ABC</Key><Value>1, 2, 3, 4, 5</Value></Parameter></Parameters>'),
-- added only 5
(3, '<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5, 6</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>'),
-- no change
(4, '<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>'),
-- no change
(5, '<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>')
SELECT * FROM #afterXML
我已经设法从每一行的特定“值”节点中提取值,检查缺少哪些数字并准备更新数据
所以我 temp_table
的数据如下:
IF OBJECT_iD('tempdb..#temp_table') IS NOT NULL
DROP TABLE #temp_table
CREATE TABLE #temp_table
(
ID int NOT NULL,
NewSetOfValues varchar(100) NOT NULL
)
INSERT INTO #temp_table (ID, NewSetOfValues)
VALUES
(1, '1, 2, 3, 4, 5'),
(2, '1, 2, 3, 4, 5'),
(3, '1, 2, 3, 5, 6')
SELECT * FROM #temp_table
但这就是我卡住的地方。
我完全不知道如何构建正确的修改方法语法以仅更新所呈现的 xml 结构中的特定“值”节点...:(
是否有一些简单的方法来处理此类更新?
提前感谢您的帮助。
诚然,这并没有按照数字的顺序对 XML 字符串进行排序,但是您应该能够按照您的需要对其进行排序:
update b
-- .modify is a special function that modifies XML in place
set SomeXMLData.modify('
replace value of
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]
with
concat (
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1],
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "3")])[1] ) then "" else ", 3" ,
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "5")])[1] ) then "" else ", 5"
)')
from @beforeXML b;
其工作方式如下:
- 我们搜索 XML 个节点,从根
/
开始,降序 Parameters
,然后 Parameter
但是这个节点必须有一个子节点 Key
其中有一个 text()="ABC"
,然后下降 /Value/text())
并取得第一个 [1]
节点。
- 将此值替换为现有值的串联,并且:
- 如果
Value
节点匹配 [contains(text()[1], "3")])[1] )
则什么都没有,否则我们添加 ", 3"
- 如果
Value
节点匹配 [contains(text()[1], "5")])[1] )
那么什么都没有,否则我们添加 ", 5"
结果:
|SomeDataXML|
----
|<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 4, 3, 5</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 4, 5, 3</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 6, 5</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>|
如果需要对数字序列进行排序,可以试试下面的解决方法。它使用 SQL Server 2017 中提供的一对方便的函数:
STRING_SPLIT()
STRING_AGG()
所有功劳归功于@Charlieface
SQL
-- DDL and sample data population, start
DECLARE @tbl Table (ID INT IDENTITY PRIMARY KEY, SomeXMLData XML NOT NULL);
INSERT INTO @tbl (SomeXMLData) VALUES
(N'<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 4</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 4, 5</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 6</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>');
-- DDL and sample data population, end
-- before
SELECT * FROM @tbl;
-- concat missing numbers
UPDATE b
SET SomeXMLData.modify('
replace value of
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]
with
concat (
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1],
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "3")])[1] ) then "" else ", 3" ,
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "5")])[1] ) then "" else ", 5"
)')
FROM @tbl AS b;
-- get sequence of numbers sorted
;WITH rs AS
(
SELECT *
, (SELECT STRING_AGG(TRIM(value), ', ') WITHIN GROUP (ORDER BY TRIM(value) ASC)
FROM STRING_SPLIT(SomeXMLData.value('(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]','VARCHAR(30)'), ',')
) AS Result
FROM @tbl
)
UPDATE rs
SET SomeXMLData.modify('
replace value of
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]
with (sql:column("Result"))');
-- after
SELECT * FROM @tbl;
我刚刚开始学习如何使用 XML 数据格式,但我已经被困在更新一些数据上了。我真的非常感谢对这个问题的一些帮助,因为我完全不知道如何处理这样的问题。
生成一些示例数据的代码:
IF OBJECT_iD('tempdb..#beforeXML') is NOT NULL
DROP TABLE #beforeXML
CREATE TABLE #beforeXML
(
ID int NOT NULL,
SomeXMLData XML NOT NULL
)
INSERT INTO #beforeXML (ID, SomeXMLData)
VALUES
(1, '<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 4</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>'),
(2, '<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 4, 5</Value></Parameter></Parameters>'),
(3, '<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 6</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>'),
(4, '<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>'),
(5, '<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>')
SELECT * FROM #beforeXML
困难的部分来了...
我需要更新“Value”节点的值,其中“Key”节点的值在同一个“Parameter”节点中为“ABC”。
如您所见,我的 xml 中有几个“参数”节点,这些节点没有特定的顺序或属性,我可以用来区分它们并确定我应该更新哪些节点。有些行没有这样的节点,有些行在“值”节点中已经有数字 3 or/and 5,所以我只需要添加一个或两个 (3 or/and 5) 不见了。
我想得到的结果:
IF OBJECT_iD('tempdb..#afterXML') IS NOT NULL
DROP TABLE #afterXML
CREATE TABLE #afterXML
(
ID int NOT NULL,
SomeXMLData XML NOT NULL
)
INSERT INTO #afterXML (ID, SomeXMLData)
VALUES
-- added both 3, 5
(1, '<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 3, 4, 5</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>'),
-- added only 3
(2, '<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter>
<Parameter><Key>ABC</Key><Value>1, 2, 3, 4, 5</Value></Parameter></Parameters>'),
-- added only 5
(3, '<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5, 6</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>'),
-- no change
(4, '<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>'),
-- no change
(5, '<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>')
SELECT * FROM #afterXML
我已经设法从每一行的特定“值”节点中提取值,检查缺少哪些数字并准备更新数据
所以我 temp_table
的数据如下:
IF OBJECT_iD('tempdb..#temp_table') IS NOT NULL
DROP TABLE #temp_table
CREATE TABLE #temp_table
(
ID int NOT NULL,
NewSetOfValues varchar(100) NOT NULL
)
INSERT INTO #temp_table (ID, NewSetOfValues)
VALUES
(1, '1, 2, 3, 4, 5'),
(2, '1, 2, 3, 4, 5'),
(3, '1, 2, 3, 5, 6')
SELECT * FROM #temp_table
但这就是我卡住的地方。
我完全不知道如何构建正确的修改方法语法以仅更新所呈现的 xml 结构中的特定“值”节点...:(
是否有一些简单的方法来处理此类更新?
提前感谢您的帮助。
诚然,这并没有按照数字的顺序对 XML 字符串进行排序,但是您应该能够按照您的需要对其进行排序:
update b
-- .modify is a special function that modifies XML in place
set SomeXMLData.modify('
replace value of
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]
with
concat (
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1],
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "3")])[1] ) then "" else ", 3" ,
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "5")])[1] ) then "" else ", 5"
)')
from @beforeXML b;
其工作方式如下:
- 我们搜索 XML 个节点,从根
/
开始,降序Parameters
,然后Parameter
但是这个节点必须有一个子节点Key
其中有一个text()="ABC"
,然后下降/Value/text())
并取得第一个[1]
节点。 - 将此值替换为现有值的串联,并且:
- 如果
Value
节点匹配[contains(text()[1], "3")])[1] )
则什么都没有,否则我们添加 ", 3" - 如果
Value
节点匹配[contains(text()[1], "5")])[1] )
那么什么都没有,否则我们添加 ", 5"
结果:
|SomeDataXML|
----
|<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 4, 3, 5</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 4, 5, 3</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 6, 5</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>|
|<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>|
如果需要对数字序列进行排序,可以试试下面的解决方法。它使用 SQL Server 2017 中提供的一对方便的函数:
STRING_SPLIT()
STRING_AGG()
所有功劳归功于@Charlieface
SQL
-- DDL and sample data population, start
DECLARE @tbl Table (ID INT IDENTITY PRIMARY KEY, SomeXMLData XML NOT NULL);
INSERT INTO @tbl (SomeXMLData) VALUES
(N'<Parameters><Parameter><Key>ABC</Key><Value>1, 2, 4</Value></Parameter><Parameter><Key>XYZ</Key><Value>A</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>KLM</Key><Value>true</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 4, 5</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 6</Value></Parameter><Parameter><Key>XYZ</Key><Value>A, C</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>XYZ</Key><Value>A</Value></Parameter><Parameter><Key>ABC</Key><Value>1, 2, 3, 5</Value></Parameter></Parameters>'),
(N'<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>');
-- DDL and sample data population, end
-- before
SELECT * FROM @tbl;
-- concat missing numbers
UPDATE b
SET SomeXMLData.modify('
replace value of
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]
with
concat (
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1],
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "3")])[1] ) then "" else ", 3" ,
if ((/Parameters/Parameter[Key[text()="ABC"]]/Value[contains(text()[1], "5")])[1] ) then "" else ", 5"
)')
FROM @tbl AS b;
-- get sequence of numbers sorted
;WITH rs AS
(
SELECT *
, (SELECT STRING_AGG(TRIM(value), ', ') WITHIN GROUP (ORDER BY TRIM(value) ASC)
FROM STRING_SPLIT(SomeXMLData.value('(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]','VARCHAR(30)'), ',')
) AS Result
FROM @tbl
)
UPDATE rs
SET SomeXMLData.modify('
replace value of
(/Parameters/Parameter[Key[text()="ABC"]]/Value/text())[1]
with (sql:column("Result"))');
-- after
SELECT * FROM @tbl;