2

I have a SQL stored procedure local variable @DocList (Declare @DocList XML) which contains the follwing XML data:

<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>

I also have an SQL table "DocAccess" which contains 0 or more rows with DocIDNumber the correlate to matching "ID" attribute values in the XML:

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

I want to apply query "select [DocIDNumber] from [tblDocAccess] where [Key] = {some arbitrary value}" against the XML in @DocList to modify attribute "Status" value from "NEW" to "OLD" for each node where attribute "ID" values matches a returned [DocIdNumber] values.

I know I could create a cursor fror the select statement, then loop to locate/update any matching node/attribute value, but that does note seem efficient.

Any assistance of suggestions would be appreciated.

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

Follow-up question: Using the XML document shown above in @DocList, and another local variable, @SearchID varchar(35) which contains a value to search for, how would I code the required {While ... Exists ... Set) logic to set Status to "OLD" for the Document with ID matching the value in @SearchID.

Please forgive my ignorance here. I have been working with SQL for many years, but this is my first attempt to update an existing XML document.

1 Answer 1

1

The XML-method .modify() allows for one change at a time. This would mean to use one statement per needed change. A CURSOR or a WHILE loop might be a good idea.

As I try to avoid procedural logic, you can have a look at these alternatives:

Shred and re-create

One approach was to shred the whole thing and recreate it from scratch:

First I create a mockup to simulate your situation

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

--Now we can read all values from the XML and re-create the XML after using CASE to set the needed status values to OLD:

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 and FLWOR approach

Alternatively you can try something along this:

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>
');

First we create a XML where we include the values from the DocAccess-table. This will look like this:

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

The XQuery will rebuild the document but will set the Status-attribute depending on the existance of a corresponding ID element in <DocAccess>.

Final statement

You can use a

  • CURSOR with separate statemets per change,
  • you can shred and re-create the XML or
  • you can use XQuery/FLWOR to re-build the XML.

It depends on your needs, which approach you prefer.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. I am very new to XML and all of it's associate access syntax and methods. You have given me a lot to analyze and make sense of.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.