【问题标题】:How to update xml in table having multiple same elements in xml, from data by joining another table如何通过加入另一个表从数据中更新在xml中具有多个相同元素的表中的xml
【发布时间】:2020-06-22 07:34:06
【问题描述】:

我有一个包含 XML 列的表。现在这个 XML 列有多个标签,比如一个数组。这些标签中的每一个都有一个属性(),我想通过加入它来从表中更新它。

Table_A如下

Id    XML
1     "<Root><Object ObjId = "1" Text = "A"><Object Id = "2" Text = "B"></Root>"
2     "<Root><Object ObjId = "1" Text = "M"><Object Id = "12" Text = "N"></Root>"

每个XML如下

<Root>
   <Object Id = "1" Text = "A">
   <Object Id = "2" Text = "B">
   <Object Id = "3" Text = "C">
   <Object Id = "4" Text = "D">
   <Object Id = "5" Text = "E">
</Root>

Table_B 我想加入以更新 XML 记录:

Table_A_Id    ObjId      Value
1             1          "Q"
1             2          "R"
2             1          "S"
2             12         "T"

我找不到在 XML.modify 方法中创建动态路径的解决方案。以下是我开始的查询,这可能解释了我想要做什么。

UPDATE TblA SET XML.modify(replace value of (/Root/Object) with Table_B.Value)
FROM TABLE_A TblA
CROSS APPLY TblA.XML.nodes('/Root/Object') AS xmlObjs(obj)
INNER JOIN Table_B TblB ON TblB.Table_A_Id = TblA.Id AND TblB.ObjId = xmlObjs.obj.value('@ObjId[1]','varchar(MAX)')

【问题讨论】:

    标签: sql-server xml tsql xquery xml.modify


    【解决方案1】:

    您似乎知道的是:.modify() 每次调用都不允许进行多次更改。您必须使用CURSORWHILE 循环才能一个接一个地更新每个事件。

    因此我建议这种方法:

    首先我们创建模型表来模拟您的问题:

    DECLARE @tblA TABLE(Id INT, [XML] XML)
    INSERT INTO @tblA VALUES
    (1,N'<Root>
           <Object Id = "1" Text = "A"/>
           <Object Id = "2" Text = "B"/>
           <Object Id = "5" Text = "E"/>
       </Root>')
    ,(2,N'<Root>
           <Object Id = "1" Text = "F"/>
           <Object Id = "2" Text = "G"/>
           <Object Id = "12" Text = "J"/>
           <Object Id = "13" Text = "J"/>
       </Root>')
    
    DECLARE @tblB TABLE(Table_A_Id INT,[ObjId] INT,[Value] VARCHAR(10));
    INSERT INTO @tblB VALUES
     (1,1 ,'Q')
    ,(1,2 ,'R')
    ,(2,1 ,'S')
    ,(2,12,'T');
    

    --查询

    WITH cte AS
    (
        SELECT tA.Id
              ,tA.[XML]
              ,(
                SELECT A.obj.value('@Id','int') AS [@Id]
                      ,COALESCE(tB.[Value],A.obj.value('@Text','varchar(max)')) AS [@Text]
                FROM tA.[XML].nodes('/Root/Object') A(obj)
                LEFT JOIN @tblB tB ON tB.Table_A_Id=tA.Id AND tB.[ObjId]=A.obj.value('@Id','int')
                FOR XML PATH('Object'),ROOT('Root'),TYPE
               ) AS NewXml
        FROM @tblA tA
    )
    UPDATE cte SET cte.[Xml]=NewXml;
    

    --检查结果

    SELECT * FROM @tblA;
    

    简而言之:

    • 我们使用可更新的 CTE 将表 A 的数据读入列表中
    • 我们使用相关子查询动态创建修改后的 XML。
    • 我们在现有 XML 的基础上编写新的 XML。

    更新

    下次请尽量避免变色龙问题...您的评论让这变成了完全不同的东西...下次请关闭一个问题,如果它按原样回答 并开始一个新问题,以防您发现最初的问题并未真正满足您的需求...

    试试这个:

    DECLARE @tblA TABLE(Id INT, [XML] XML)
    INSERT INTO @tblA VALUES
    (1,N'<Root> 
           <AnotherTag val="abc"/> 
           <AnotherTag val="DEF"/> 
           <Object Id =  "1" Text = "F" OtherProperty = "123" /> 
           <Object Id =  "2" Text = "G" SampleProperty = "Anything" DataProperty="Sample Data" /> 
           <Object Id = "12" Text = "I" OtherProperty = "123"/> 
           <Object Id = "13" Text = "J" DataProperty = "Sample"/> 
         </Root>')
    ,(2,N'<Root>
           <Object Id = "1" Text = "F"/>
           <Object Id = "2" Text = "G"/>
           <Object Id = "12" Text = "I"/>
           <Object Id = "13" Text = "J"/>
       </Root>')
    
    DECLARE @tblB TABLE(Table_A_Id INT,[ObjId] INT,[Value] VARCHAR(10));
    INSERT INTO @tblB VALUES
     (1,1 ,'Q')
    ,(1,2 ,'R')
    ,(2,1 ,'S')
    ,(2,12,'T');
    
    WITH cte AS
    (
        SELECT ta.Id
              ,ta.[XML]
              ,Combined.[CombXml].query('<Root>
                                         {
                                            for $elmt in /combined/embedded/Root/*
                                            let $bVal := /combined/b_data[ObjId[1] = $elmt/@Id]/Value/text()
                                            return
                                            if(local-name($elmt) eq "Object") then 
                                                <Object Id="{$elmt/@Id}" Text="{if(empty($bVal)) then $elmt/@Text else $bVal}">
                                                {$elmt/@*[local-name() != "Id" and local-name() != "Text"]}
                                                </Object>
                                            else
                                                $elmt
                                         }
                                         </Root>') AS NewXml
        FROM @tblA ta
        OUTER APPLY(SELECT (SELECT [ObjId],[Value] 
                            FROM @tblB tb 
                            WHERE tb.Table_A_Id=ta.Id 
                            FOR XML PATH('b_data'),TYPE) AS [*]
                           ,ta.[XML] AS [embedded]
                           FOR XML PATH(''),ROOT('combined'),TYPE) Combined([CombXml])
    )
    UPDATE cte SET [XML] = NewXml;
    
    SELECT * FROM @tblA;
    

    背后的想法:

    为了使用 XQuery-FLWOR,我们需要将辅助数据放入 XML。

    APPLY 将创建这样的 XML:

    <combined>
      <b_data>
        <ObjId>1</ObjId>
        <Value>Q</Value>
      </b_data>
      <b_data>
        <ObjId>2</ObjId>
        <Value>R</Value>
      </b_data>
      <embedded>
        <Root>
          <AnotherTag val="abc" />
          <AnotherTag val="DEF" />
          <Object Id="1" Text="F" OtherProperty="123" />
          <Object Id="2" Text="G" SampleProperty="Anything" DataProperty="Sample Data" />
          <Object Id="12" Text="J" OtherProperty="123" />
          <Object Id="13" Text="J" DataProperty="Sample" />
        </Root>
      </embedded>
    </combined>
    

    针对这个组合的 XML,我们可以使用 .query() 运行 FLWOR 查询。

    • 此查询将遍历&lt;Root&gt; 下的所有节点
    • 您的辅助数据对应的&lt;Value&gt;(在XML 中为&lt;b_data&gt;)分配给$bVal
    • 现在我们检查当前元素的名称是否为&lt;Object&gt;
    • 如果是这样,我们直接编写属性IdText 并添加所有其他属性而不查看它们。
    • 如果不是,我们就按原样返回 $elmt

    Id=1 之后的结果如下所示:

    <Root>
      <AnotherTag val="abc" />
      <AnotherTag val="DEF" />
      <Object Id="1" Text="Q" OtherProperty="123" />
      <Object Id="2" Text="R" SampleProperty="Anything" DataProperty="Sample Data" />
      <Object Id="12" Text="I" OtherProperty="123" />
      <Object Id="13" Text="J" DataProperty="Sample" />
    </Root>
    

    您可以看到,“Q”和“R”在 Id 为 1 或 2 的地方发生了变化 - 根据侧面数据。

    我必须承认,这变得很复杂......

    根据您的真实数据和 XML 的复杂性,使用.modify() 的循环方法可能会更好...

    【讨论】:

    • 很好的答案,我这边+1!
    • 您好 Shnugo,感谢您的回复。在实际场景中,我对 XML 有一点不同。我希望你也能在这方面帮助我。 ' XML 中的某些对象标记具有其他属性。但并非所有人都具有这些属性。请帮忙
    猜你喜欢
    • 2018-01-26
    • 1970-01-01
    • 2011-04-29
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    相关资源
    最近更新 更多