使用XML-DML在现有实例中动态插入XML节点

时间:2016-11-04 13:20:50

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

如果能够在SQL Server(2012)中的现有xml实例中动态插入xml节点,我还无法找到任何文档。我知道您可以插入或替换条件值,但如果插入位置可以根据某些条件动态完成,则似乎没有任何文档。例如,让我说我有这个XML-DML调用:

SET @xml.modify('insert <SecondaryContact><Name>{ sql:variable("@contactName") }</Name>
                    <Phone>{ sql:variable("@contactPhone") }</Phone>
                    <Email>{ sql:variable("@contactEmail") }</Email></SecondaryContact>
                    after (/Project/PrimaryContact)[1]');

after关键字后面列出的节点是有条件的修改是否有效?以下是我所指的一个例子:

SET @xml.modify('insert <TechnicalContact><Name>{ sql:variable("@contactName") }</Name>
                    <Phone>{ sql:variable("@contactPhone") }</Phone>
                    <Email>{ sql:variable("@contactEmail") }</Email></TechnicalContact>
                    after (
                        if(count(/Project/SecondaryContact) = 0)
                        then (/Project/PrimaryContact)[1]
                        else (/Project/SecondaryContact)(1)
                    )');

是动态选择位置以在XML DML语句之外包含IF..ELSE语句的唯一方法,还是我的示例XML-DML有效?

编辑示例XML:

<root>
    ...
    <PrimaryContact Id="1234">
        <Name>John Doe</Name>
        <Phone>555-555-5555</Phone>
        <Email>somewhere@test.com</Email>
    </PrimaryContact>
    <SecondaryContact Id="1236">   <--OPTIONAL
        <Name>John Doe1</Name>
        <Phone>555-555-5556</Phone>
        <Email>somewhere1@test.com</Email>
    </SecondaryContact>
    <TechnicalContact Id="2234"> <--OPTIONAL
        <Name>John Doe2</Name>
        <Phone>555-555-5255</Phone>
        <Email>somewhere3@test.com</Email>
    </TechnicalContact>
    ...
</root>

我知道这种结构并不理想。它应该是<Contacts><Contact Type="PRIMARY" Id="1234">...</Contact>...</Contacts>,但想要查看DML语句中是否可以使用动态插入位置。对于这个问题,光标使用是可以的,因为它将用于一次性更新。

3 个答案:

答案 0 :(得分:3)

这个建议并不完全相同,因为它会在文档后面的任何内容之后插入,而不是在SecondaryContact之后插入,但我怀疑在你的情况下这是同样的事情:

SET @xml.modify('insert 
    <TechnicalContact><Name>{ sql:variable("@contactName") }</Name>
    <Phone>{ sql:variable("@contactPhone") }</Phone>
    <Email>{ sql:variable("@contactEmail") }</Email></TechnicalContact>

    after (/Project/*[
        local-name(.) = "SecondaryContact" 
        or local-name(.) = "PrimaryContact" 
    ])[last()]
');

或者:

if @xml.value('count(/Project/SecondaryContact)', 'int') = 0
begin
  SET @xml.modify('insert <TechnicalContact><Name>{ sql:variable("@contactName") }</Name>
                <Phone>{ sql:variable("@contactPhone") }</Phone>
                <Email>{ sql:variable("@contactEmail") }</Email></TechnicalContact>
                after (/Project/PrimaryContact)[1]
                ');
end else begin
  SET @xml.modify('insert <TechnicalContact><Name>{ sql:variable("@contactName") }</Name>
                <Phone>{ sql:variable("@contactPhone") }</Phone>
                <Email>{ sql:variable("@contactEmail") }</Email></TechnicalContact>
                after (/Project/SecondaryContact)[1]
                ');
end

答案 1 :(得分:1)

在您提供更多示例(请参阅我的评论)之前,我能想到的最好的方法是创建要在外部插入的节点并将其作为最后一个插入

但是 - 非常肯定 - 有更好的方法......

DECLARE @contactName NVARCHAR(100)='TestName';
DECLARE @contactPhone NVARCHAR(100)='TestPhone';
DECLARE @contactEmail NVARCHAR(100)='TestEmail';

DECLARE @tbl TABLE(ID INT IDENTITY,Descr VARCHAR(100),XmlColumn XML);
INSERT INTO @tbl VALUES
 ('With secondary'
 ,N'<Project>
<PrimaryContact>test Primary</PrimaryContact>
<SecondaryContact>test Secondary</SecondaryContact>
</Project>')
,('Only primary'
,N'<Project>
<PrimaryContact id="prim">test Primary</PrimaryContact>
</Project>');

UPDATE @tbl SET XmlColumn.modify
(
    N'insert sql:column("x.NodeToInsert") as last into (/Project)[1]'
)
FROM @tbl
CROSS APPLY
(
    SELECT
    (
        SELECT(
                    SELECT @contactName AS [Name]
                          ,@contactPhone AS [Phone]
                          ,@contactEmail AS [Email]
                    WHERE XmlColumn.exist('/Project/SecondaryContact')=0
                    FOR XML PATH('SecondaryContact'),TYPE
               ) AS [node()]
              ,(
                    SELECT @contactName AS [Name]
                          ,@contactPhone AS [Phone]
                          ,@contactEmail AS [Email]
                    WHERE XmlColumn.exist('/Project/SecondaryContact')=1
                    FOR XML PATH('TechnicalContact'),TYPE
               ) AS [node()]
        FOR XML PATH(''),TYPE
    ) AS NodeToInsert
) AS x

SELECT * FROM @tbl

更新

另一种方法是:使用CTE粉碎XML,使用.query()获取所有未受影响的节点,.value()以提取受影响的值。然后使用简单的SELECT ... FOR XML PATH()语句以您需要的方式重建XML ...

答案 2 :(得分:1)

您可以按此顺序SecondaryContact PrimaryContactinsert <TechnicalContact> <Name>{ sql:variable("@contactName") }</Name> <Phone>{ sql:variable("@contactPhone") }</Phone> <Email>{ sql:variable("@contactEmail") }</Email> </TechnicalContact> after ( /Project/SecondaryContact, /Project/PrimaryContact )[1] ,并在第一次出现后添加节点。

myf<>();