T-SQL XML:如果有多个具有相同名称的节点,如何更新一个节点中的值?

问题描述

我刚刚开始学习如何使用 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;

工作方式如下:

  1. 我们搜索 XML 节点,从根 / 开始,降序 Parameters,然后是 Parameter 但这个节点必须有一个子节点 Key,它有一个 {{ 1}},然后下降 text()="ABC" 并取第一个 /Value/text()) 节点。
  2. 用现有值的串联替换此值,并且:
  3. 如果 [1] 节点匹配 Value 则什么都没有,否则我们添加 ",3"
  4. 如果 [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;