2015-06-18 3 views
2

Я пытаюсь изменить XML-строку, чтобы заменить значение узла чем-то другим. Конечной целью будет массовое шифрование номеров учетных записей в столбце, но на данный момент я просто пытаюсь заменить его в строке XML.TSQL Xpath Изменить строку XML

Когда я выполняю это, я получаю «Команда завершена успешно», хотя я пытаюсь вернуть данные.

DECLARE @xml XML = '<optional><account>155555555</account></optional>' 


SET @xml.modify('replace value of (/optional/account/text())[1] with "' + (
       SELECT 'ABC-' + ParamValues.item.value('.', 'varchar(max)') 
       FROM @xml.nodes('/optional/account') AS ParamValues(item) 
     )+ '"') 

SELECT @xml 

Любые мысли о том, почему это не заменяет номер счета в строке XML с ABC-Account Number Here и вернуть его?

+0

Я не знаю, что означает 'SET @ xml.modify ...', но то, что он делает, это остановить процесс на своих дорожках. Если вы замените 'SELECT @ xml' на' SELECT 1', вы получите тот же результат. Если вы пытаетесь заменить содержимое тега XML всем этим текстом, я думаю, вам придется составить его в виде строки, а затем создать целый новый XML. –

+0

Ну, если я выберу внутренний выбор и просто заменил его на некоторый текст, который я набираю, он отлично работает, перезагружая @XML var со следующим значением.Однако, когда вы пытаетесь динамически обновлять его из другого оператора select, он сталкивается с проблемой – SBB

+0

'SET @ xml.modify ('заменить значение (/ optional/account/text()) [1] на concat (" ABC- " , (/ optional/account) [1]) ') ' – har07

ответ

3

Чтобы ответить на конкретный вопрос, «почему он не заменяет номер счета», это потому, что параметры функции XML должны быть строковые литералы. Вы создаете параметр для функции XML modify, что вызывает условие ошибки.

Обычно SQL Server выдает сообщение об ошибке, когда вы не используете строковый литерал в параметре функции XML, но в этом случае его следует путать из-за вызова функции nodes().

Вот простой пример с строковым литералом, который работает, как Вы отмечаете в вашем комментарии:

DECLARE @xml XML = '<optional><account>155555555</account></optional>' 
SET @xml.modify('replace value of (/optional/account/text())[1] with "1"'); 
SELECT @xml; 

Однако, если вы пытаетесь построить параметр функции modify XML, как в приведенном ниже примере, это не получится:

DECLARE @xml XML = '<optional><account>155555555</account></optional>' 
SET @xml.modify('replace value of (/optional/account/text())[1] with "' + '1' + '"'); 
SELECT @xml; 

ошибка:

Msg 8172, Level 16, State 1, Line 2 The argument 1 of the XML data type method "modify" must be a string literal.

Очевидно, что если вы получите немного фантазер, бросая nodes() в там, он путает SQL Server и squelches состояние ошибки:

DECLARE @xml XML = '<optional><account>155555555</account></optional>' 
SET @xml.modify('replace value of (/optional/account/text())[1] with "' + (SELECT '1' FROM @xml.nodes('/optional/')) + '"'); 
SELECT @xml; 

Это не ошибка, но скорее завершается перед отображением любых данных и просто печатает:

Command(s) completed successfully.

Таким образом, вы не можете построить параметр функции XML. Тем не менее, вы все равно можете использовать внешнюю информацию. Kiran Hedge showed how с использованием sql:variable()XQuery extension function.

Однако на самом деле вам не обязательно идти на эту длину. Вы используете данные из XML, к которым имеет собственный процессор XML. Таким образом, вы можете сделать что-то вроде этого:

DECLARE @newXmlSingleReplacement XML = '<optional><account>155555555</account></optional>'; 
SET @newXmlSingleReplacement.modify('replace value of ((/optional/account)/text())[1] with fn:concat("ABC-",((/optional/account)/text())[1])'); 
SELECT @newXmlSingleReplacement; 

Таким образом, либо решение Kiran, либо это решение отлично подходит для вашего упрощенного примера. Но у вас, вероятно, есть более сложный XML-документ. Вероятно, он имеет несколько «строк», которые вы хотите изменить.

Если попробовать тот же код из выше документа XML с несколькими номерами счетов, только первый номер заменен:

DECLARE @newXmlSingleReplacement XML ='<optional><account>155555555</account></optional><optional><account>255555555</account></optional>'; 
    SET @newXmlSingleReplacement.modify('replace value of ((/optional/account)/text())[1] with fn:concat("ABC-",((/optional/account)/text())[1])'); 
    SELECT @newXmlSingleReplacement; 

Результаты:

<optional> 
    <account>ABC-155555555</account> 
</optional> 
<optional> 
    <account>255555555</account> 
</optional> 

Вы можете подумать, вы могли бы просто удалите индекс (1) и затроните все строки. К сожалению, согласно Microsoft's documentation, первый параметр replace value of «должен идентифицировать только один узел». Таким образом, вы не можете взять список всех номеров учетных записей и действовать по этому поводу.

Этот пример:

DECLARE @newXmlSingleReplacement XML ='<optional><account>155555555</account></optional><optional><account>255555555</account></optional>'; 
SET @newXmlSingleReplacement.modify('replace value of ((/optional/account)/text()) with fn:concat("ABC-",((/optional/account)/text()))'); 
SELECT @newXmlSingleReplacement; 

Результаты в этой ошибке:

Msg 2389, Level 16, State 1, Line 2 XQuery [modify()]: 'concat()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'

Так вместо того, чтобы вы могли перебирает все ваши "строки" и выполнить modify() операции каждый раз. Вам понадобится способ отслеживать прогресс с помощью счетчика. Вот пример этого, используя немного более сложный XML, чтобы доказать концепцию.

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>'; 
DECLARE @newxml XML = @xml, @AccountCount int = 0, @Counter int = 0; 
SET @AccountCount = @newxml.value('fn:count(//account)','int'); 
WHILE @Counter <= @AccountCount 
BEGIN 
    SET @Counter = @Counter + 1; 
    SET @newxml.modify('replace value of ((/optional/account)[position()=sql:variable("@Counter")]/text())[1] with fn:concat("ABC-",((/optional/account)[position()=sql:variable("@Counter")]/text())[1])'); 
END 
SELECT @newxml; 

Результаты:

<optional> 
    <other>Test123</other> 
    <account>ABC-155555555</account> 
</optional> 
<optional> 
    <other>Test321</other> 
    <account>ABC-255555555</account> 
</optional> 

Конечно, мы предпочли бы, чтобы избежать петель в коде SQL, если мы можем. Отдельные операторы, которые работают на наборах, часто дают лучшую производительность.

Один из вариантов заключается в том, чтобы уничтожить ваш XML и изменить его, при этом корректируя значения в процессе. Недостатком этого метода является то, что вы должны знать специфику XML для его восстановления. Это также может быть дорогостоящим и запутанным заявлением, в зависимости от сложности XML-документа. Вот пример:

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>'; 
SELECT 
    v.value('(./other)[1]','varchar(500)') AS other, 
    'ABC-' + v.value('(./account)[1]','varchar(500)') AS account 
FROM  @xml.nodes('/optional') AS T(v) 
FOR XML PATH ('optional'), TYPE; 

Но это не единственный способ реформировать XML. Вы можете восстановить его с помощью самой XML-системы и ее FLWOR statement support. Например:

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>'; 
SELECT @xml.query (' 
    for $optional in //optional 
    return 
     <optional> 
      {$optional/other} 
      <account>ABC-{$optional/account/text()}</account> 
     </optional> 

'); 

Но опять-таки это требует знания и ручного воссоздания структуры XML. Есть способы избежать этого. Следующий пример требует минимального знания существующего XML. Он по существу пересекает узлы на уровне узла учетной записи и заменяет их только в том случае, если они называются «учетными записями».

DECLARE @xml XML = '<optional><other>Test123</other><account>155555555</account></optional><optional><other>Test321</other><account>255555555</account></optional>'; 
SELECT @xml.query (' 
    for $optional in //optional 
    return 
     <optional> 
      { 
       for $subnode in $optional/* 
        return 
         if (fn:local-name($subnode) = "account") 
         then  
          <account>ABC-{$subnode/text()}</account> 
         else 
          $subnode 
      } 
     </optional> 
'); 

на основе сырого теста с SET STATISTICS TIME ON; на этих очень маленьких документов XML пример, оказывается, что nodes() размельчения и восстанавливающий немного быстрее. Он также имеет план простейших и самых низких затрат с существенным запасом.

0

Вы не можете заменить значение другим оператором select. Вы можете использовать только SQL переменную для динамических значений ниже

DECLARE @xml XML = '<optional><account>155555555</account></optional>'; 
DECLARE @val VARCHAR(MAX) 
SELECT @val ='ABC-' + @xml.value('(/optional/account/node())[1]', 'nvarchar(max)') 
SET @xml.modify('replace value of (/optional/account/text())[1] with sql:variable("@val")'); 
SELECT @xml; 
+0

Итак, я предполагаю, что здесь передумал. Давайте просто скажем, что я хотел найти столбец в таблице, называемой' details'. В этом столбце он содержит строку XML, как указано выше, где номера учетных записей различны. Мне нужно заменить это значение на 'ABC-существующее значение здесь' – SBB

Смежные вопросы