1

我有一个包含 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)')
4

1 回答 1

2

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

因此,我建议这种方法:

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

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().

  • 此查询将遍历下面的所有节点<Root>
  • 您的侧数据的对应<Value>(在 XML 中<b_data>)被分配给$bVal.
  • 现在我们检查当前元素的名称是否为<Object>.
  • 如果是这样,我们编写属性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()可能会更好......

于 2020-06-22T15:56:26.447 回答