Необходимо обновить несколько узлов в строке XML, используя значения из запроса SQL - PullRequest
2 голосов
/ 12 июня 2019

У меня есть локальная переменная хранимой процедуры SQL @DocList (Declare @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, соответствующим значению атрибута «ID» в XML:

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

Я хочу применить запрос "выберите [DocIDNumber] из [tblDocAccess], где [Key] = {некоторое произвольное значение}" для XML в @DocList, чтобы изменить значение атрибута «Status» с «NEW» на «OLD» для каждого узла, где значения атрибута "ID" соответствуют возвращенным значениям [DocIdNumber].

Я знаю, что мог бы создать курсор для оператора select, а затем выполнить цикл, чтобы найти / обновить любой соответствующий значение узла / атрибута, но это замечание кажется эффективным.

Любая помощь предложений будет принята.

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

Дополнительный вопрос: используя XML-документ, показанный выше в @DocList, и другую локальную переменную @SearchID varchar (35), которая содержит значение для поиска, как бы я кодировал требуемое {Пока ... существует. ..Установите) логику, чтобы установить для параметра Status значение «СТАРЫЙ» для документа, идентификатор которого соответствует значению в @ SearchID.

Пожалуйста, прости мое невежество здесь. Я работал с SQL много лет, но это моя первая попытка обновить существующий документ XML.

1 Ответ

1 голос
/ 13 июня 2019

XML-метод .modify() допускает одно изменение за раз. Это означало бы использовать одно утверждение для каждого необходимого изменения. CURSOR или WHILE цикл может быть хорошей идеей.

Поскольку я пытаюсь избежать процедурной логики, вы можете взглянуть на эти альтернативы:

Измельчите и воссоздайте

Один из подходов заключался в том, чтобы уничтожить все это и восстановить его с нуля:

Сначала я создаю макет для имитации вашей ситуации

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 и воссоздать XML после использования CASE для установки необходимых status значений на 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 и 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.

Это зависит от ваших потребностей, какой подход вы предпочитаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...