【问题标题】:Replace multiple values in XML column based on a mapping table基于映射表替换 XML 列中的多个值
【发布时间】:2019-05-07 09:52:06
【问题描述】:

假设我有一个名为tblMap 的映射表,它只是将旧属性 ID 映射到新属性 ID(oldID -> newID)。值得注意的是:newID 不包含在 oldID 的列表中。

然后我有一个表tblData,其中包含一个xml 字符串,该字符串具有多个属性ids。我想用tblMap 中的newID 替换所有当前属性ids。如果在tblMap 中找不到 id 映射,那么它应该保持原样。关于如何实现这一点的任何提示?

我尝试了什么:

我试图使用XMLText.modify('replace value of ...') 强制执行某些操作,如This StackOverflow Article 中所述,但未能成功使其工作。

CREATE TABLE tblmap (
  oldid INT, 
  newid INT
)
GO

INSERT INTO tblMap
VALUES
( 58, 1002),
( 85, 5002),
( 70, 3202),
(2, 2340),
(5, 7432)
GO

CREATE TABLE tblData ( [SourceID] int, [SourceRecID] bigint, [Value] xml )
GO

INSERT INTO tblData
VALUES
( 1, 0, N'<attributes><attribute id="58" value="0" /><attribute id="86" value="1" /><attribute id="85" value="1" /><attribute id="70" value="0" /><attribute id="38" value="0" /><attribute id="68" value="0" /><attribute id="42" value="1" /><attribute id="67" value="1" /><attribute id="62" value="1" /></attributes>' ), 
( 1, 686, N'<attributes><attribute id="1" value="0.25" /><attribute id="4" value="1" /><attribute id="10" value="3" /><attribute id="11" value="1" /><attribute id="12" value="6" /></attributes>' ), 
( 1, 687, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="9" value="1" /><attribute id="10" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' ), 
( 1, 688, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' )


SELECT *
FROM tblMap
GO

SELECT *
FROM tblData
GO

为方便起见,我在此处构建了所有架构/示例数据: https://rextester.com/MUMI61854

【问题讨论】:

  • 那么,对于您的样本数据,您的预期结果是什么?
  • 在每个属性元素中将 id= 替换为 id=
  • 另外,SQL Server 的版本是 2008 还是 2012?
  • SQL Server 2012
  • @Denis 现在你有 2008 年或 2012 年,我想我会根据你说 2008 年没有的评论更新你的问题?

标签: sql sql-server xml sql-server-2012


【解决方案1】:

我会尝试完全重新创建整个 XML(或者更确切地说是 /attributes 节点)并使用新值更新表:

declare @tblmap table (oldid INT, newid INT);

INSERT INTO @tblMap
VALUES
( 58, 1002),
( 85, 5002),
( 70, 3202),
(2, 2340),
(5, 7432);

declare @tblData table ([SourceID] int, [SourceRecID] bigint, [Value] xml);

INSERT INTO @tblData
VALUES
( 1, 0, N'<attributes><attribute id="58" value="0" /><attribute id="86" value="1" /><attribute id="85" value="1" /><attribute id="70" value="0" /><attribute id="38" value="0" /><attribute id="68" value="0" /><attribute id="42" value="1" /><attribute id="67" value="1" /><attribute id="62" value="1" /></attributes>' ), 
( 1, 686, N'<attributes><attribute id="1" value="0.25" /><attribute id="4" value="1" /><attribute id="10" value="3" /><attribute id="11" value="1" /><attribute id="12" value="6" /></attributes>' ), 
( 1, 687, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="9" value="1" /><attribute id="10" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' ), 
( 1, 688, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' );

SELECT * FROM @tblMap;
SELECT * FROM @tblData;

-- Update table with new XML
with cte as (
select d.*, (
    select isnull(m.newid, a.c.value('./@id', 'int')) as [@id], a.c.value('./@value', 'nvarchar(max)') as [@value]
    from d.Value.nodes('/attributes[1]/attribute') a(c)
        left join @tblmap m on m.oldid = a.c.value('./@id', 'int')
    for xml path('attribute'), type, root('attributes')
    ) as [NewValue]
from @tblData d
)
update c set Value = NewValue
from cte c;

-- New version
select * from @tblData;

(我已将您的表转换为表变量,因为它在实例上留下了零占用空间。其他一切都是一样的。)

不幸的是,如果您的实际 XML 架构比您的示例显示的更复杂,并且在 /attributes 节点下涉及其他不可预测的元素和/或属性,则此方法可能难以实现。在这种情况下,我会推荐使用 FLWOR(至少对我而言,这很慢而且很难编写)或 cursored update。

调试:

-- Update table with new XML
with cte as (
select d.*, (
    select isnull(m.newid, a.c.value('./@id', 'int')) as [@id], a.c.value('./@value', 'nvarchar(max)') as [@value]
    from d.Value.nodes('/attributes[1]/attribute') a(c)
        left join @tblmap m on m.oldid = a.c.value('./@id', 'int')
    for xml path('attribute'), type, root('attributes')
    ) as [NewValue]
from @tblData d
)
SELECT c.SourceID,
   c.SourceRecID,
   c.Value,
   c.NewValue
from cte c;

【讨论】:

  • 很棒的解决方案(我这边+1)!在 SQL-Server 级别上与我的完全一样,而我在XQuery 中处理整个事情。目前没有时间,但我很感兴趣,哪种方法更快......必须稍后测试......
  • 我也喜欢这个。我的 XML 就是我发布的内容,没有什么不同 - 谢天谢地。会试一试。我看到的唯一问题(仅查看此查询)是,如果 XML 类似于 &lt;attributes /&gt;,那么这将为 NewValue 返回“NULL”,我的表将拒绝该值,因为 Value 不是 NULL(在我的示例中意识到它不是那样设置的)但是我可以进行预处理,因为无论如何存储这些空属性是没有意义的,所以我可以在此查询之前删除它们。顺便说一句,很好的解决方案!非常容易调试,因为您可以并排查看旧值和新值!
  • @Denis, null 结果很容易绕过 - 只需将更新行替换为 update c set Value = isnull(NewValue, N'&lt;attributes /&gt;') 就足够了(现在无法测试)。
【解决方案2】:

我的建议调用XQuery 来救援(txh Roger Wolf 声明的表变量,也使用了它们...):

declare @tblmap table (oldid INT, newid INT);

INSERT INTO @tblMap
VALUES
( 58, 1002),
( 85, 5002),
( 70, 3202),
(2, 2340),
(5, 7432);

declare @tblData table ([SourceID] int, [SourceRecID] bigint, [Value] xml);

INSERT INTO @tblData
VALUES
( 1, 0, N'<attributes><attribute id="58" value="0" /><attribute id="86" value="1" /><attribute id="85" value="1" /><attribute id="70" value="0" /><attribute id="38" value="0" /><attribute id="68" value="0" /><attribute id="42" value="1" /><attribute id="67" value="1" /><attribute id="62" value="1" /></attributes>' ), 
( 1, 686, N'<attributes><attribute id="1" value="0.25" /><attribute id="4" value="1" /><attribute id="10" value="3" /><attribute id="11" value="1" /><attribute id="12" value="6" /></attributes>' ), 
( 1, 687, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="9" value="1" /><attribute id="10" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' ), 
( 1, 688, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' );

--查询将一次性完成整个过程

WITH CombineThem AS
(
    SELECT d.SourceID
          ,d.SourceRecID
          ,d.[Value]
          ,(SELECT
               (SELECT * 
                FROM @tblMap 
                FOR XML PATH('map'),ROOT('maps'),TYPE)
              ,[Value] AS [*]
             FOR XML PATH('Combined'),TYPE) AS Combined
    FROM @tblData d
)
,updateableCTE AS
(
    SELECT ct.[Value]
          ,ct.Combined
           .query('<attributes>
                   {
                    for $attr in /Combined/attributes/attribute
                    return <attribute id="{
                                           (
                                            /Combined/maps/map[oldid[1]=$attr/@id]/newid
                                            ,$attr/@id
                                           )[1]
                                          }" 
                                      value="{$attr/@value}"/> 
                   }  
                   </attributes>') NewValue
    FROM CombineThem ct
)
UPDATE updateableCTE SET [Value]=NewValue;

--检查结果

SELECT * FROM @tblData;

一些解释

为了使用XQuery 中的映射和数据,我在第一个CTE 中创建了一个组合XML。这将包括完整的&lt;attributes&gt; 元素和&lt;maps&gt; 元素。

.query() 将遍历属性并搜索&lt;maps&gt; 以寻找合适的重新映射。奇迹发生在(val1,val2)[1]。这类似于COALESCE()。它将选择第一个 non-null 值,即合适的新 id 或现有值。

最后一步不是使用.modify() 更新XML,而是一次性将[Value] 列设置为新创建的XML。

【讨论】:

  • 丹尼斯,仅供参考 @Shnugo 使用的方法称为“FLWOR”(发音为“flavour”),我在回答中提到了它。你可以(几乎)用它做任何事情,但学习曲线确实很陡峭。以下是一些描述:docs.microsoft.com/en-us/sql/xquery/…
  • 有趣的方法
【解决方案3】:

老实说,我不是 100% 相信 ORDER BY (SELECT NULL) 的可靠性,但是,除了 希望 顺序是节点的顺序之外,我没有太多选择。

反正解决方案涉及到动态SQL;可能有一种“更好”的方式来做到这一点,但如果有我不知道。我建议先做一些体面的测试,但是,这似乎会得到你想要的结果:

DECLARE @SQL nvarchar(MAX);
SET @SQL = STUFF((SELECT NCHAR(10) +
                         N'UPDATE tblData' + NCHAR(10) + 
                         N'SET [Value].modify(''replace value of (/attributes/attribute/@id)[' + CONVERT(varchar(4),ROW_NUMBER() OVER (PARTITION BY D.SourceID, D.SourceRecID ORDER BY (SELECT NULL))) + N'] with "' + CONVERT(varchar(4),ISNULL(M.newid,V.AA.value('@id','int'))) + N'"'')' + NCHAR(10) +
                         N'WHERE SourceID = ' + CONVERT(varchar(4),D.SourceID) + NCHAR(10) +
                         N'  AND SourceRecID = ' + CONVERT(varchar(4),D.SourceRecID) + N';'
                  FROM tblData D
                       CROSS APPLY D.[Value].nodes('attributes/attribute') V(AA)
                       LEFT JOIN tblmap M ON V.AA.value('@id','int') = M.oldid
                  FOR XML PATH(N'')),1,1,N'');

EXEC sp_executesql @SQL;

【讨论】:

  • 我正在考虑某种 WHILE 循环,但我会尝试处理这个循环,看看它是否有效。
  • 我个人更喜欢构建动态 sql,而不是 WHILE 循环。在某些情况下,我发现动态 SQL 可以构建大批量,比 WHILE/CURSOR 快得多。
  • 我想知道使用replace value of (/attributes/attribute/@id)[.=58][1] with "1002)"是否会更好(与xml中旧值的平等比较)
  • @Age丹尼斯?您的数据中没有 @Age 元素。
  • 抱歉,我在另一篇 SO 文章中看到一个想法,复制/粘贴速度太快
猜你喜欢
  • 1970-01-01
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 2018-03-15
  • 2018-11-06
  • 2022-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多