【问题标题】:Need to update multiple nodes in a XML string using values from an SQL query需要使用 SQL 查询中的值更新 XML 字符串中的多个节点
【发布时间】:2023-04-01 17:52:01
【问题描述】:

我有一个 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 将每个节点的属性“状态”值从“新”修改为“旧” 属性“ID”值与返回的 [DocIdNumber] 值匹配。

我知道我可以为 select 语句创建一个游标,然后循环查找/更新任何匹配的 节点/属性值,但这确实看起来很有效。

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

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

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

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

【问题讨论】:

    标签: xml tsql


    【解决方案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
    

    --现在我们可以从 XML 中读取所有值,并在使用 CASE 将所需的 status 值设置为 OLD 后重新创建 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 将重建文档,但会根据 &lt;DocAccess&gt; 中对应 ID 元素的存在设置 Status-属性。

    最后声明

    你可以用一个

    • CURSOR 每次更改都有单独的 statemets,
    • 您可以分解并重新创建 XML 或
    • 您可以使用 XQuery/FLWOR 重新构建 XML。

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

    【讨论】:

    • 谢谢。我对 XML 非常陌生,所有这些都是关联的访问语法和方法。你给了我很多分析和理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2018-05-27
    • 2017-06-02
    相关资源
    最近更新 更多