如何通过加入另一个 table 从数据中更新 table 中具有多个相同元素的 xml

How to update xml in table having multiple same elements in xml, from data by joining another table

我有 table,其中有 XML 列。现在这个 XML 列有多个标签,就像一个数组。这些标签中的每一个都有一个属性(),我想通过加入它来从 table 更新它。

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)')

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

因此我建议采用这种方法:

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

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 将 table A 的数据读入列表
  • 我们使用 相关子查询 即时创建修改后的 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;

背后的想法:

我们需要将辅助数据 放入 中 XML 以便使用 XQuery-FLWOR。

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 查询
  • 此查询将运行遍历<Root>
  • 下的所有节点
  • 你的副数据对应的<Value>(在XML其<b_data>中)被赋值给$bVal.
  • 现在我们检查当前元素的名称是否为 <Object>
  • 如果是这样,我们直接写属性 IdText 并添加所有其他属性而不查看它们。
  • 如果没有,我们只是 return $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() 的循环方法可能更好...