Чтобы ответить на конкретный вопрос, «почему он не заменяет номер счета», это потому, что параметры функции 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()
размельчения и восстанавливающий немного быстрее. Он также имеет план простейших и самых низких затрат с существенным запасом.
Я не знаю, что означает 'SET @ xml.modify ...', но то, что он делает, это остановить процесс на своих дорожках. Если вы замените 'SELECT @ xml' на' SELECT 1', вы получите тот же результат. Если вы пытаетесь заменить содержимое тега XML всем этим текстом, я думаю, вам придется составить его в виде строки, а затем создать целый новый XML. –
Ну, если я выберу внутренний выбор и просто заменил его на некоторый текст, который я набираю, он отлично работает, перезагружая @XML var со следующим значением.Однако, когда вы пытаетесь динамически обновлять его из другого оператора select, он сталкивается с проблемой – SBB
'SET @ xml.modify ('заменить значение (/ optional/account/text()) [1] на concat (" ABC- " , (/ optional/account) [1]) ') ' – har07