根据映射表替换XML列中的多个值

时间:2018-12-05 18:49:20

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

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

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

我尝试过的事情:

我正在尝试使用This StackOverflow Article中所述的XMLText.modify('replace value of ...')来强制执行某些操作,但是并没有成功使其正常工作。

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

3 个答案:

答案 0 :(得分:2)

我将尝试完全重新创建整个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(速度慢,至少对我来说很难编写)或游标更新。

调试:

-- 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 :(得分: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。这将包括完整的<attributes>元素和一个<maps>元素。

.query()将遍历属性,并在<maps>中搜索合适的重新映射。魔术发生在(val1,val2)[1]中。行为类似于COALESCE()。它将选择第一个 non-null 值,它是合适的新id或现有值。

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

答案 2 :(得分:1)

老实说,这里的ORDER BY (SELECT NULL)可靠性不是100%,但是,除了希望顺序是节点顺序以外,我没有其他选择。

无论如何,该解决方案涉及动态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;