【问题标题】:How to update a table based on an XML parameter如何根据 XML 参数更新表
【发布时间】:2012-05-31 14:57:41
【问题描述】:

我有一个表,我想根据 XML 参数中的值更新其中一个 varchar 字段。

我有下表:

ID  Constraint_Value
1   (OldVal_1) (OldVal_2)
2   (OldVal_2) (OldVal_1)

我想使用以下 XML 来更新 Constraint_Value 字段:

<qaUpdates>
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate>
</qaUpdates>

更新后,我的目标是:

ID    Constraint_Value
1     (NewVal_1) (NewVal_2)
2     (NewVal_2) (NewVal_1)

以下 SQL 说明了我的问题(无需任何设置即可在 SQL Management Studio 中运行):

IF OBJECT_ID('tempdb..#tmpConstraint') IS NOT NULL DROP TABLE #tmpConstraint
GO 

CREATE TABLE tempdb..#tmpConstraint ( constraint_id INT PRIMARY KEY, constraint_value varchar(256) )
GO

insert into #tmpConstraint
values (1, '(OldVal_1) (OldVal_2)')

insert into #tmpConstraint
values (2, '(OldVal_2) (OldVal_1)')

select * from #tmpConstraint

declare @myXML XML
set @myXML = N'<qaUpdates>
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate>
</qaUpdates>'

update c
set constraint_value = REPLACE(constraint_value, Child.value('(old)[1]', 'varchar(50)'), Child.value('(new)[1]', 'varchar(50)'))
from #tmpConstraint c
cross join @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 

select * from #tmpConstraint

这给出了结果:

(Before)
1   (OldVal_1) (OldVal_2)
2   (OldVal_2) (OldVal_1)

(After)
1   (NewVal_1) (OldVal_2)
2   (OldVal_2) (NewVal_1)

如您所见,OldVal_1 已更新。 OldVal_2 保持不变。

如何使用 xml 参数中指定的所有元素更新字段?

【问题讨论】:

    标签: sql-server xml sql-server-2008 tsql


    【解决方案1】:

    使用递归 cte 可以让我得到您正在寻找的结果。如下图所示。但至少它不是游标/while循环;)

    declare @tmpConstraint table (ID int , Constraint_Value varchar(256))
    insert into @tmpConstraint values 
    (1, '(OldVal_1) (OldVal_2)'),
    (2, '(OldVal_2) (OldVal_1)')
    
    declare @myXML XML
    set @myXML = N'<qaUpdates>
        <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>
        <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate>
    </qaUpdates>'
    
    declare @xmlData table (oldValue varchar(256), newValue varchar(256))
    insert into @xmlData 
    select 
        oldValue = Child.value('(old)[1]', 'varchar(50)'), 
        newValue = Child.value('(new)[1]', 'varchar(50)')
    from @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 
    

    以上只是为以下设置。

    ;with cte (ID, Constraint_Value, CLevel)
    as
    (
        select c.ID, c.Constraint_Value, 1
        from @tmpConstraint c
    
        union all
    
        select p.ID, cast(replace(p.Constraint_Value, x.oldValue, x.newValue) as varchar(256)), p.CLevel + 1
        from cte p
        join @xmlData x on p.Constraint_Value like '%' + x.oldValue + '%'
    )
    update c
    set c.Constraint_Value = t.Constraint_Value
    from @tmpConstraint c
    join (
        select 
            *,
            rn = row_number() over (partition by ID order by CLevel desc)
        from cte
    ) t on t.ID = c.ID and rn = 1
    
    select * from @tmpConstraint
    

    【讨论】:

    • 很好的答案,@mouters。使用 cte's 是一个非常聪明的解决方案。谢谢。
    • 这是一个不错的解决方案,但它实际上是隐藏在 CTE 中的一个循环。另请注意,CTE 中的行数可能比原始表多得多(对于具有 n 个匹配值的每一行,您将得到 1 + n + n(n-1) + n(n-1)(n- 2)+...n! 行)。另请注意,默认的 OPTION (MAXRECURSION n) 值为 100。但是,对于每行匹配很少的中等大小的表,我喜欢它。
    【解决方案2】:

    我认为这里的问题与 XML 无关。这是一个单一的 UPDATE 只会更新每行一次,而不管存在多少连接行。我认为您可以添加 WHERE 子句和 WHILE 循环来获取所有替换:

    WHILE @@ROWCOUNT>0
    BEGIN
      update c 
      set constraint_value = REPLACE(constraint_value, Child.value('(old)[1]', 'varchar(50)'), Child.value('(new)[1]', 'varchar(50)')) 
      from #tmpConstraint c 
      cross join @myXML.nodes('/qaUpdates/qaUpdate') as N(Child)  
      WHERE constraint_value LIKE '%' + Child.value('(old)[1]', 'varchar(50)') + '%'
    END
    

    请确保这遵循设置@@RowCount 的语句。

    【讨论】:

      【解决方案3】:

      我意识到这已经得到了回答,但我很想知道是否有办法在不使用 cte 的情况下做到这一点。无论如何,更大的问题实际上是您将 2 条数据存储在同一列/行中。再加上您无法在单个更新语句中两次更新同一行的事实导致了您的问题。无论如何我的方法是这样的(我提前为复杂性道歉):

      DECLARE @tmpConstraint TABLE (
          constraint_id INT PRIMARY KEY
          ,constraint_value VARCHAR(256)
          )
      
      INSERT INTO @tmpConstraint
      VALUES (
          1
          ,'(OldVal_1) (OldVal_2)'
          )
      
      INSERT INTO @tmpConstraint
      VALUES (
          2
          ,'(OldVal_2) (OldVal_1)'
          )
      
      INSERT INTO @tmpConstraint
      VALUES (
          3
          ,'(OldVal_3) (OldVal_21) (OldVal_1)'
          )
      
      DECLARE @myXML XML
      
      SET @myXML = N'<qaUpdates>     <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>     <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate> </qaUpdates>'
      
      SELECT *
      FROM @tmpConstraint
      
      UPDATE C
      SET constraint_value = c.New_Val
      FROM (
          SELECT Constraint_ID UpdID
              ,Constraint_value
              ,STUFF((
                      SELECT (' ' + New_value)
                      FROM (
                          --Converts XML into a Table effectively splitting the string
                          SELECT constraint_id
                              ,t.value('.', 'varchar(200)') Current_value
                              ,Coalesce(Nullif('(' + new + ')', '()'), t.value('.', 'varchar(200)')) New_Value
                          FROM
                              --Converts single column into an xml document to split rows. Uses a blank space as the identifer of rows
                              (
                              SELECT constraint_id
                                  ,convert(XML, ('<R>' + replace(constraint_value, ' ', '</R><R>')) + '</R>') xmldoc
                              FROM @tmpConstraint
                              ) AS a
                          CROSS APPLY a.xmldoc.nodes('./R') AS b(t)
                          --Join to table containing proposed changes based on value to change            
                          LEFT JOIN (
                              SELECT Child.value('./old[1]', 'varchar(100)') old
                                  ,Child.value('./new[1]', 'varchar(100)') new
                              FROM @myXML.nodes('/qaUpdates/qaUpdate') AS N(Child
                              )
                          ) q2 ON '(' + old + ')' = t.value('.', 'varchar(200)')
                      ) Modified WHERE Modified.constraint_id = base.constraint_id FOR XML path(''))
              ,1,1,'') New_Val
      FROM @tmpConstraint Base ) c
      
      SELECT *
      FROM @tmpConstraint
      

      它看起来比现在更乱,如果你有一些 UDF 的话可以清理干净。但基本上我将你的多值列分成多行。转这个

      1   (OldVal_1) (OldVal_2)
      2   (OldVal_2) (OldVal_1)
      3   (OldVal_3) (OldVal_21) (OldVal_1)
      

      进入这个

      1   (OldVal_1)
      1   (OldVal_2)
      2   (OldVal_2)
      2   (OldVal_1)
      3   (OldVal_3)
      3   (OldVal_21)
      3   (OldVal_1)
      

      我对 xml 文件做同样的事情。把它变成这样的Key数据对集

      OldVal_1    NewVal_1
      OldVal_2    NewVal_2
      

      将它与之前创建的表连接起来以获得这个(复制没有确定替换的原始值)

      1   (OldVal_1)  (NewVal_1)
      1   (OldVal_2)  (NewVal_2)
      2   (OldVal_2)  (NewVal_2)
      2   (OldVal_1)  (NewVal_1)
      3   (OldVal_3)  (OldVal_3)
      3   (OldVal_21) (OldVal_21)
      3   (OldVal_1)  (NewVal_1)
      

      将分隔的行重新组合成一个字符串,再次按约束 id 分组,如下所示:

      1   (OldVal_1) (OldVal_2)       (NewVal_1) (NewVal_2)
      2   (OldVal_2) (OldVal_1)       (NewVal_2) (NewVal_1)
      3   (OldVal_3) (OldVal_21) (OldVal_1)   (OldVal_3) (OldVal_21) (NewVal_1)
      

      然后在我的 from 语句中使用它来更新原始表中的数据。无论如何,我意识到代码可以稍微清理一下,甚至可能简化,但这就是我想出的概念。真正更大的问题是您如何存储这些数据。但是我不知道你的情况,所以我保留判断。

      【讨论】:

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