需要使用SQL查询中的值更新XML字符串中的多个节点

时间:2019-06-12 18:34:56

标签: xml tsql

我有一个SQL存储过程局部变量@DocList(声明@DocList XML) 其中包含以下XML数据:

<JobList ListItems="7">
  <Job JobFriendlyName="EMAIL INVOICES">
    <DocumentList>
      <Document Doc="1" ID="5280301.2019050148902.00020" Date="05-03-2019" Status="NEW" />
      <Document Doc="2" ID="5280301.2019050148902.00022" Date="05-03-2019" Status="NEW" />
      <Document Doc="3" ID="5280301.2019050148902.00023" Date="05-03-2019" Status="NEW" />
      <Document Doc="4" ID="5280301.2019050104301.00055" Date="05-02-2019" Status="NEW" />
      <Document Doc="5" ID="5280301.2019050104301.00056" Date="05-02-2019" Status="NEW" />
    </DocumentList>
  </Job>
  <Job JobFriendlyName="INVOICES">
    <DocumentList>
      <Document Doc="6" ID="5280300.2019050148901.00001" Date="05-03-2019" Status="NEW" />
      <Document Doc="7" ID="5280300.2019050148901.00002" Date="05-03-2019" Status="NEW" />
    </DocumentList>
  </Job>
</JobList>

我还有一个SQL表“ DocAccess”,其中包含0行或更多行,其中DocIDNumber与XML中匹配的“ ID”属性值相关:

TABLE [tblDocAccess]
(
    [Key] varachar(10),
    [DocIDNumber] [varchar](35),
    [DocLastOpenDtg] [smalldatetime]
)

我要应用查询“从[tblDocAccess]中选择[DocIDNumber],其中[Key] = {某个任意值}” 针对@DocList中的XML将每个节点的属性“ Status”值从“ NEW”修改为“ OLD” 属性“ ID”值与返回的[DocIdNumber]值匹配。

我知道我可以在select语句之前创建一个游标,然后循环查找/更新任何匹配项 node / attribute值,但这确实有效。

任何建议的帮助将不胜感激。

==================================

后续问题:使用@DocList中上面显示的XML文档以及另一个包含要搜索的值的局部变量@SearchID varchar(35),我将如何编码所需的{While ... Exists。 .. Set)逻辑将ID与@SearchID中的值匹配的Document的Status设置为“ OLD”。

请原谅我的无知。我已经使用SQL多年了,但这是我第一次尝试更新现有的XML文档。

1 个答案:

答案 0 :(得分:1)

XML方法.modify()允许一次更改。这意味着对每个需要的更改使用一个语句。 CURSORWHILE循环可能是个好主意。

在我尝试避免过程逻辑时,您可以看一下这些替代方法:

切碎并重新创建

一种方法是切碎整个内容并从头开始重新创建:

首先,我创建一个模型来模拟您的情况

DECLARE @DocList XML=
N'<JobList ListItems="7">
  <Job JobFriendlyName="EMAIL INVOICES">
    <DocumentList>
      <Document Doc="1" ID="5280301.2019050148902.00020" Date="05-03-2019" Status="NEW" />
      <Document Doc="2" ID="5280301.2019050148902.00022" Date="05-03-2019" Status="NEW" />
      <Document Doc="3" ID="5280301.2019050148902.00023" Date="05-03-2019" Status="NEW" />
      <Document Doc="4" ID="5280301.2019050104301.00055" Date="05-02-2019" Status="NEW" />
      <Document Doc="5" ID="5280301.2019050104301.00056" Date="05-02-2019" Status="NEW" />
    </DocumentList>
  </Job>
  <Job JobFriendlyName="INVOICES">
    <DocumentList>
      <Document Doc="6" ID="5280300.2019050148901.00001" Date="05-03-2019" Status="NEW" />
      <Document Doc="7" ID="5280300.2019050148901.00002" Date="05-03-2019" Status="NEW" />
    </DocumentList>
  </Job>
</JobList>';

DECLARE @mockupDocAccess TABLE
(
    [Key] varchar(10),
    [DocIDNumber] [varchar](35),
    [DocLastOpenDtg] [smalldatetime]
);

INSERT INTO @mockupDocAccess VALUES('SomeKey','5280301.2019050148902.00022',GETDATE())   --Doc 2
                                  ,('SomeKey','5280301.2019050104301.00055',GETDATE())   --Doc 4
                                  ,('SomeKey','5280300.2019050148901.00001',GETDATE())   --Doc 6
                                  ,('OtherKey','5280301.2019050104301.00056',GETDATE()); --Doc 5

-现在,我们可以使用CASE将所需的status值设置为OLD后,从XML中读取所有值并重新创建XML:

DECLARE @Key VARCHAR(10)='SomeKey';

WITH AllEmailInvoices AS
(
    SELECT d.value('@Doc','int') AS Doc
          ,d.value('@ID','nvarchar(35)') AS ID
          ,d.value('@Date','nvarchar(10)') AS [Date] --unconverted
          ,CASE WHEN EXISTS(SELECT 1 FROM @mockupDocAccess da WHERE da.DocIDNumber=d.value('@ID','nvarchar(35)') AND da.[Key]=@Key) THEN 'OLD' ELSE d.value('@Status','nvarchar(10)') END AS [Status]

    FROM @DocList.nodes('/JobList/Job[@JobFriendlyName="EMAIL INVOICES"]/DocumentList/Document') A(d)
)
,AllInvoices AS
(
    SELECT d.value('@Doc','int') AS Doc
          ,d.value('@ID','nvarchar(35)') AS ID
          ,d.value('@Date','nvarchar(10)') AS [Date] --unconverted
          ,CASE WHEN EXISTS(SELECT 1 FROM @mockupDocAccess da WHERE da.DocIDNumber=d.value('@ID','nvarchar(35)') AND da.[Key]=@Key) THEN 'OLD' ELSE d.value('@Status','nvarchar(10)') END AS [Status]

    FROM @DocList.nodes('/JobList/Job[@JobFriendlyName="INVOICES"]/DocumentList/Document') A(d)
)
SELECT @DocList.value('(/JobList/@ListItems)[1]','int') AS [@ListItems]
      ,(
            SELECT 'EMAIL INVOICES' AS [@JobFriendlyName]
                   ,(
                        SELECT Doc AS [@Doc]
                              ,ID AS [@ID]
                              ,[Date] AS [@Date]
                              ,[Status] AS [@Status]      
                        FROM AllEmailInvoices
                        FOR XML PATH('Document'),ROOT('DocumentList'),TYPE
                    )
            FOR XML PATH('Job'),TYPE
        )
        ,(
            SELECT 'INVOICES' AS [@JobFriendlyName]
                   ,(
                        SELECT Doc AS [@Doc]
                              ,ID AS [@ID]
                              ,[Date] AS [@Date]
                              ,[Status] AS [@Status]      
                        FROM AllInvoices
                        FOR XML PATH('Document'),ROOT('DocumentList'),TYPE
                    )
            FOR XML PATH('Job'),TYPE
        )
FOR XML PATH('JobList');

XQuery和FLWOR方法

或者,您可以尝试以下操作:

DECLARE @Key VARCHAR(10)='SomeKey';

SELECT
(
    SELECT (SELECT DocIDNumber AS ID FROM @mockupDocAccess WHERE [Key]=@Key FOR XML PATH(''),TYPE) DocAccess
          ,@DocList
    FOR XML PATH(''),TYPE
).query
(N'
    <JobList> {/JobList/@*}
    {
    for $j in /JobList/Job 
    return
        <Job> {$j/@*}
        {
            <DocumentList>
            {
            for $d in $j/DocumentList/Document
            return
                <Document Doc="{$d/@Doc}" 
                          ID="{$d/@ID}" 
                          Date="{$d/@Date}" 
                          Status="{if(/DocAccess[ID=$d/@ID]) then "OLD" else xs:string($d/@Status)}" />
            }
            </DocumentList>
        }
        </Job>
    }
    </JobList>
');

首先,我们创建一个XML,其中包含DocAccess表中的值。看起来像这样:

<DocAccess>
  <ID>5280301.2019050148902.00022</ID>
  <ID>5280301.2019050104301.00055</ID>
  <ID>5280300.2019050148901.00001</ID>
</DocAccess>
<JobList ListItems="7">
  <!-- Your Content here -->
</JobList>

XQuery将重建文档,但是将根据Status中相应ID元素的存在来设置<DocAccess>属性。

最终声明

您可以使用

  • CURSOR,每次更改具有单独的状态参数,
  • 您可以粉碎并重新创建XML或
  • 您可以使用XQuery / FLWOR重新构建XML。

这取决于您的需求,您更喜欢哪种方法。