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;

其工作方式如下:

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