【问题标题】:T-SQL XML : how to update value in one node if there are more nodes with identical names?T-SQL XML:如果有更多具有相同名称的节点,如何更新一个节点中的值?
【发布时间】:2021-02-07 21:46:20
【问题描述】:

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

现在最困难的部分来了......

我需要在同一“参数”节点内更新“值”节点的值,其中“键”节点的值 =“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, '<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 tsql xquery sql-server-2017


    【解决方案1】:

    诚然,这不会按数字顺序对 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>|
    

    【讨论】:

    • 好答案,我这边+1!
    • Charlieface,请投票:feedback.azure.com/forums/908035-sql-server/suggestions/… 它成为 SQL Server 最受欢迎的请求之一。目前的投票总数为 511,并且还在增加。
    • @Charlieface。非常感谢您的帮助。这正是我在这种情况下所需要的。数字序列不是主要问题,因此您提供的解决方案足以处理我的数据。
    【解决方案2】:

    如果要对数字序列进行排序,可以尝试以下解决方案。它使用了 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;
    

    【讨论】:

    猜你喜欢
    • 2013-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    相关资源
    最近更新 更多