问题描述
我刚刚开始学习如何使用 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,4,5</Value></Parameter></Parameters>'),(3,'<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1,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,(5,'<Parameters><Parameter><Key>XYZ</Key><Value>B</Value></Parameter><Parameter><Key>KLM</Key><Value>true</Value></Parameter></Parameters>')
SELECT * FROM #beforeXML
现在是困难的部分......
我需要在同一个“参数”节点中更新“值”节点的值,其中“键”节点的值=“ABC”。
如您所见,我的 xml 中有几个“参数”节点,这些节点没有特定的顺序或属性,我可以用来区分它们并确定应该更新哪些节点。有些行没有这样的节点,有些行在“值”节点中已经有数字 3 或/和 5,所以我只需要添加一种或两种情况(3 或/和 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,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,-- added only 5
(3,5,-- no change
(4,-- 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,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","5")])[1] ) then "" else ",5"
)')
from @beforeXML b;
工作方式如下:
- 我们搜索 XML 节点,从根
/
开始,降序Parameters
,然后是Parameter
但这个节点必须有一个子节点Key
,它有一个 {{ 1}},然后下降text()="ABC"
并取第一个/Value/text())
节点。 - 用现有值的串联替换此值,并且:
- 如果
[1]
节点匹配Value
则什么都没有,否则我们添加 ",3" - 如果
[contains(text()[1],"3")])[1] )
节点匹配Value
则什么都没有,否则我们添加 ",5"
结果:
[contains(text()[1],"5")])[1] )
,
如果要对数字序列进行排序,可以尝试以下解决方案。它使用了 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,4,5</Value></Parameter></Parameters>'),(N'<Parameters><Parameter><Key>KLM</Key><Value>false</Value></Parameter><Parameter><Key>ABC</Key><Value>1,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,(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],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;