替换T-SQL中节点数组中的一个节点的值

时间:2017-10-28 02:59:19

标签: sql-server xml tsql xpath xquery

如果我在表中有一个列为xml,具有以下节点结构:

<ArrayOfPickListObjectBaseModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PickListObjectBaseModel>
    <MasterObjectId>3964405</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>405716</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>5872525</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
</ArrayOfPickListObjectBaseModel>

我可以成功检索具有特定MasterObjectId

的所有记录
SELECT  p.MyPrimaryKey, 
        m.value('(MasterObjectId)[1]', 'INT')  as MasterObjectID,
        p.MasterObjectIDs
    FROM PickList p
    CROSS APPLY MasterObjectIDs.nodes('//PickListObjectBaseModel') AS t1(m)
    WHERE m.value('(MasterObjectId)[1]', 'INT') = 3964405

但如果需要,我如何在所有匹配记录中替换该数字? 因此,在上面的示例中,将3964405替换为999

这是错误的语法,但我认为它类似于:

    DECLARE @oldId INT = 13131180;
    DECLARE @newId INT = 99999;

    UPDATE PickList
        SET MasterObjectIDs.modify('replace value of 
    (//PickListObjectBaseModel/MasterObjectId/text()=sql:variable("@oldId"))[1] with sql:variable("@newId")');

2 个答案:

答案 0 :(得分:2)

以下是基于值替换文本节点的完整解决方案 FROM部分会加快速度,因为它会减少受影响的行数,否则会对所有行执行操作

    DECLARE @oldId INT = 13131180;
    DECLARE @newId INT = 99999;

    UPDATE PickList
        SET MasterObjectIDs.modify('replace value of 
        (//PickListObjectBaseModel/MasterObjectId[text() = sql:variable("@oldId")]/text())[1]
            with sql:variable("@newId")')
        FROM PickList p
                CROSS APPLY MasterObjectIDs.nodes('//PickListObjectBaseModel') AS t1(m)
                WHERE m.value('(MasterObjectId)[1]', 'INT') = @oldId

答案 1 :(得分:1)

很好,你找到了一个解决方案(投了票),只是一些提示:

包含三行的虚拟表:

DECLARE @DummyTable TABLE(ID INT IDENTITY, Comment VARCHAR(100), MasterObjectIDs XML);
INSERT INTO @DummyTable VALUES
('Your example', N'<ArrayOfPickListObjectBaseModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PickListObjectBaseModel>
    <MasterObjectId>3964405</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>405716</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>5872525</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
</ArrayOfPickListObjectBaseModel>'
)
,('More than one target', N'<ArrayOfPickListObjectBaseModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PickListObjectBaseModel>
    <MasterObjectId>3964405</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>405716</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>3964405</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
</ArrayOfPickListObjectBaseModel>'
),
('no target', N'<ArrayOfPickListObjectBaseModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PickListObjectBaseModel>
    <MasterObjectId>1111</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>405716</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
  <PickListObjectBaseModel>
    <MasterObjectId>5872525</MasterObjectId>
    <IsSelected>false</IsSelected>
  </PickListObjectBaseModel>
</ArrayOfPickListObjectBaseModel>'); 

- 变量

DECLARE @oldId INT = 3964405;
DECLARE @newId INT = 99999;

- 我使用xml.exist(),这比你过滤目标行的方法更快:

UPDATE @DummyTable
        SET MasterObjectIDs.modify('replace value of 
        (//PickListObjectBaseModel/MasterObjectId[text() = sql:variable("@oldId")]/text())[1]
            with sql:variable("@newId")')
FROM @DummyTable p
WHERE p.MasterObjectIDs.exist('/ArrayOfPickListObjectBaseModel/PickListObjectBaseModel/MasterObjectId[text()=sql:variable("@oldId")]') = 1;

- 结果

SELECT @@ROWCOUNT;         --good the filter hits only 2 rows
SELECT * FROM @DummyTable; --bad, the XML more than one occurances is not edited everywhere...

如果您要查找的ID可能会在一个XML中多次出现,那么您的方法将不适用于所有这些,仅适用于第一个...

您可以在循环中运行语句,直到exist()返回0,或者您可能会破坏XML并从头开始重建它,如下所示:

WITH UpdateableCTE AS
(
    SELECT p.MasterObjectIds AS Original
          ,B.NewXML
    FROM @DummyTable AS p
    CROSS APPLY
    (
        SELECT
        (
            SELECT CASE WHEN plo.value(N'(MasterObjectId/text())[1]','int')=@oldId THEN @newId ELSE plo.value(N'(MasterObjectId/text())[1]','int') END AS MasterObjectId
                  ,plo.value(N'(IsSelected/text())[1]','nvarchar(max)') AS IsSelected
            FROM MasterObjectIDs.nodes(N'/ArrayOfPickListObjectBaseModel/PickListObjectBaseModel') AS A(plo)
            FOR XML PATH(N'PickListObjectBaseMode'),ROOT(N'ArrayOfPickListObjectBaseModel'),TYPE
        )
    ) AS B(NewXML)
    WHERE p.MasterObjectIDs.exist('/ArrayOfPickListObjectBaseModel/PickListObjectBaseModel/MasterObjectId[text()=sql:variable("@oldId")]') = 1
)
UPDATE UpdateableCTE SET Original=NewXML;

- 只有2行受影响

SELECT @@ROWCOUNT;         --good the filter hits only 2 rows
SELECT * FROM @DummyTable; --all occurances are switched...