2015-06-18 61 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 

任何想法,爲什麼這不與ABC-Account Number Here替換XML字符串中的賬號和返回呢?

+0

我不知道'SET @ xml.modify ...'是做什麼的,但是它正在做什麼就是停止它的進程。如果你用SELECT 1替換SELECT @ xml,你會得到相同的結果。如果您試圖用所有文本替換XML標記的內容,我認爲您必須將其作爲字符串進行組合,然後創建一些全新的XML。 –

+0

那麼,如果我拿出內部選擇,並用我輸入的一些文本替換它,它完全正確地重置@XML var與下一個值。但是,當您嘗試從另一個select語句動態更新它時,它會遇到問題 – SBB

+0

SET @ xml.modify('用/ concat(「ABC-」替換(/ optional/account/text())[1] ,(/可選/帳戶)[1])')' – har07

回答

3

爲了回答您的具體問題,「爲什麼沒有更換賬戶號」,這是因爲XML函數的參數必須是字符串文字。您正在構建modify XML函數的參數,該函數會導致錯誤情況。

通常情況下,當您在XML函數參數中未使用字符串文字時,SQL Server會引發錯誤,但在這種情況下,由於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 documentationreplace 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-Existing Value Here'替換那個值 – SBB