Как использовать XML-модификацию T-SQL ('replace value of ...)' для поиска / замены пустых атрибутов - PullRequest
0 голосов
/ 27 ноября 2018

Это проблема из двух частей.У меня есть таблица со столбцом XML, содержащая данные компонента и атрибута для частей.Один из атрибутов необходимо обновить для всех компонентов и атрибутов во всех записях.Я нашел частичное решение, используя технику из приведенной ниже статьи Technet: https://social.technet.microsoft.com/wiki/contents/articles/28601.t-sql-tips-search-and-replace-string-from-multiple-nodes-within-a-xml-document.aspx Где оно не хватает:

  1. В случаях, когда PrintCode является пустой строкой (PrintCode = ""), подходтерпит неудачу, оставляя оригинальную пустую строку вместо замены ее значением "I".Это связано с тем, что функция contains () всегда возвращает TRUE, когда $ arg2 является строкой нулевой длины.
  2. Цикл WHILE EXISTS работает для преобразования только одного «старого» значения в одно «новое» значение.Я надеялся, что смогу использовать таблицу сопоставления и обновил все атрибуты за один проход.Я пробовал несколько других подходов, но не нашел решения с одним проходом.

Это упрощенная форма данных.На самом деле существует гораздо больше записей, которые необходимо переназначить.

У меня есть версия грубой силы, которая работает для переназначения всех значений, кроме строки нулевой длины, но опять-таки предпочла бы выполнить все обновления в одномзаявление, если возможно.

Пример кода:

    DECLARE @Map TABLE ([Old] VARCHAR(1) NOT NULL, [New] VARCHAR(1) NOT NULL)
insert @Map ([Old], [New])
Values   ('D', 'E'),('3', 'I'),('', 'I');

DECLARE  @ConfigMaster TABLE (
    [ConfigCode] [nvarchar](15) NOT NULL,
    [ConfigMaster] [xml] NOT NULL
    )
INSERT @configMaster (ConfigCode, [ConfigMaster])
values('TestPart01', '<ConfigMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Components>
    <Component Name="FRSTK">
      <Attributes>
        <Attribute Name="O-FLGOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="22" />
        <Attribute Name="O-GRDLGT" Value="0" OptionListId="" PrintCode="" DataEntryOrder="0" />
        <Attribute Name="O-LEADLG" Value="72" OptionListId="" PrintCode="D" DataEntryOrder="4" />
        <Attribute Name="O-LEADTP" Value="R" OptionListId="1F3" PrintCode="D" DataEntryOrder="3" />
        <Attribute Name="O-LOCOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="24" />
        <Attribute Name="O-CODE" Value="N18A13" OptionListId="1F1" PrintCode="D" DataEntryOrder="1" />
        <Attribute Name="O-CONOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="25" />
        <Attribute Name="O-FITOPT" Value="Y" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="23" />
        <Attribute Name="O-FITTIN" Value="BG" OptionListId="1F5" PrintCode="D" DataEntryOrder="32" />
        <Attribute Name="O-FLANGE" Value="" OptionListId="1F2" PrintCode="" DataEntryOrder="0" />
      </Attributes>
    </Component>
  </Components>
</ConfigMaster>')
select * from @ConfigMaster; --before

WHILE EXISTS
(
SELECT  *
        FROM    @ConfigMaster
        WHERE   ConfigMaster.exist('//Component/Attributes/Attribute[ contains(@PrintCode, "D")]') = 1
)
BEGIN
         UPDATE [m]
         SET ConfigMaster.modify('replace value of 
                                    (//Component/Attributes/Attribute[ contains(@PrintCode , "D")]/@PrintCode)[1] 
                                  with sql:column("New") ')
         FROM
         (
              SELECT  ConfigMaster,
                        t.u.value('./@PrintCode[1]','varchar(1)') AS OrigValue
              FROM    @ConfigMaster as [p]
              CROSS APPLY p.ConfigMaster.nodes('/ConfigMaster/Components/Component/Attributes/Attribute[ contains(@PrintCode , "D")]') as t(u)
         ) as [m]
         Inner join @Map as s on s.Old = m.OrigValue

END
select * from @ConfigMaster --after pass 1: "D" updated to "E"
WHILE EXISTS
(
SELECT  *
        FROM    @ConfigMaster
        WHERE   ConfigMaster.exist('//Component/Attributes/Attribute[ contains(@PrintCode, "3")]') = 1
)
BEGIN
         UPDATE [m]
         SET ConfigMaster.modify('replace value of (//Component/Attributes/Attribute[ contains(@PrintCode , "3")]/@PrintCode)[1] with sql:column("New") ')
         FROM
         (
              SELECT  ConfigMaster,
                        t.u.value('./@PrintCode[1]','varchar(1)') AS OrigValue
              FROM    @ConfigMaster as [p]
              CROSS APPLY p.ConfigMaster.nodes('/ConfigMaster/Components/Component/Attributes/Attribute[ contains(@PrintCode , "3")]') as t(u)
         ) as [m]
         Inner join @Map as s on s.Old = m.OrigValue

END
select * from @ConfigMaster --after pass 2: "3" updated to "I"
WHILE EXISTS
(
SELECT  *
        FROM    @ConfigMaster
        WHERE   ConfigMaster.exist('//Component/Attributes/Attribute[contains(@PrintCode , "")]') = 1 --the empty string "" has special meaning to the contains() function
)
BEGIN
         UPDATE [m]
         SET ConfigMaster.modify('replace value of (//Component/Attributes/Attribute[ contains(@PrintCode , "")]/@PrintCode)[1] with sql:column("New") ')
         FROM
         (
              SELECT  ConfigMaster,
                        t.u.value('./@PrintCode[1]','varchar(1)') AS OrigValue
              FROM    @ConfigMaster as [p]
              CROSS APPLY p.ConfigMaster.nodes('/ConfigMaster/Components/Component/Attributes/Attribute[ contains(@PrintCode , "")]') as t(u)
         ) as [m]
         Inner join @Map as s on s.Old = isnull(m.OrigValue,'')
      break --this will not exit otherwise...                 
END
select * from @ConfigMaster --after pass 3: no change

Заранее спасибо!

Клейтон

1 Ответ

0 голосов
/ 28 ноября 2018

Я ненавижу циклы в SQL-сервере ... Иногда мы не можем избежать этого, но в большинстве случаев есть другие подходы, и в большинстве случаев эти подходы на основе множеств лучше:

Вы, кажется, ненужно объявить пространства имен, чтобы я их отпустил ...

Я немного уменьшил это, так что это может не полностью соответствовать вашим потребностям.Дайте мне знать, если чего-то не хватает:

DECLARE  @ConfigMaster TABLE (
    [ConfigCode] [nvarchar](15) NOT NULL,
    [ConfigMaster] [xml] NOT NULL
    )
INSERT @configMaster (ConfigCode, [ConfigMaster])
values('TestPart01', '<ConfigMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Components>
    <Component Name="FRSTK">
      <Attributes>
        <Attribute Name="O-FLGOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="22" />
        <Attribute Name="O-GRDLGT" Value="0" OptionListId="" PrintCode="" DataEntryOrder="0" />
        <Attribute Name="O-LEADLG" Value="72" OptionListId="" PrintCode="D" DataEntryOrder="4" />
        <Attribute Name="O-LEADTP" Value="R" OptionListId="1F3" PrintCode="D" DataEntryOrder="3" />
        <Attribute Name="O-LOCOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="24" />
        <Attribute Name="O-CODE" Value="N18A13" OptionListId="1F1" PrintCode="D" DataEntryOrder="1" />
        <Attribute Name="O-CONOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="25" />
        <Attribute Name="O-FITOPT" Value="Y" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="23" />
        <Attribute Name="O-FITTIN" Value="BG" OptionListId="1F5" PrintCode="D" DataEntryOrder="32" />
        <Attribute Name="O-FLANGE" Value="" OptionListId="1F2" PrintCode="" DataEntryOrder="0" />
      </Attributes>
    </Component>
  </Components>
</ConfigMaster>')
select * from @ConfigMaster; --before

- безобразная грубая сила, но easy-cheesy-one-line ...

UPDATE @ConfigMaster SET ConfigMaster=
REPLACE(REPLACE(REPLACE(CAST(ConfigMaster AS NVARCHAR(MAX)),' PrintCode="D"',' PrintCode="E"'),' PrintCode="3"',' PrintCode="I"'),' PrintCode=""',' PrintCode="I"');

--XQuery, если ограничения приемлемы для вас (при условии, что их может быть больше, чем один <Component>):

UPDATE @ConfigMaster SET ConfigMaster=
ConfigMaster.query
(N' 
  <ConfigMaster>
    <Components>
    {
        for $comp in //ConfigMaster/Components/Component
        return
        <Component>{$comp/@Name}
        <Attributes>
        {
            for $node in $comp/Attributes/Attribute
            return
            <Attribute>
            {
                for $attr in $node/@*
                return
                if(local-name($attr)!="PrintCode") then 
                    $attr
                else
                    attribute PrintCode {if($attr="D") then "E" else "I"}
            }
            </Attribute>
        }
        </Attributes>
        </Component>
    }
    </Components>
    </ConfigMaster>
'); 

- и, наконец, декомпозиция / перекомпоновка (при условии, что может быть несколько компонентов)

WITH comp AS
(
    SELECT comp.value(N'@Name',N'nvarchar(max)') AS ComponentName
          ,comp.query(N'*') AS Children
    FROM @ConfigMaster 
    CROSS APPLY ConfigMaster.nodes(N'/ConfigMaster/Components/Component') A(comp)
)
SELECT ComponentName AS [ComponentName/@Name]
      ,(
        SELECT attr.value(N'@Name',N'nvarchar(max)') AS [@Name]
              ,attr.value(N'@Value',N'nvarchar(max)') AS [@Value]
              ,attr.value(N'@OptionListId',N'nvarchar(max)') AS [@OptionListId]
              ,CASE attr.value(N'@PrintCode',N'nvarchar(max)') WHEN 'D' THEN 'E' ELSE 'I' END AS [@PrintCode]
              ,attr.value(N'@DataEntryOrder',N'nvarchar(max)') AS [@DataEntryOrde]
        FROM Children.nodes(N'Attributes/Attribute') A(attr)
        FOR XML PATH('Attribute'),ROOT('Attributes'),TYPE     
       )
FROM comp
FOR XML PATH('Components'),ROOT('ConfigMaster')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...