2013-04-09 86 views
2

我想在XML文檔上有select語句,並且一列應該返回每個節點的路徑。如何從XMLType節點提取元素路徑?

例如,假定這將導致

column_value 
-------- 
<user><name>user1</name></user> 
<user><name>user2</name></user> 
<user><name>user3</name></user> 
<user><name>user4</name></user> 

數據

SELECT * 
FROM TABLE(XMLSequence(
    XMLTYPE('<?xml version="1.0"?> 
    <users><user><name>user1</name></user> 
      <user><name>user2</name></user> 
      <group> 
       <user><name>user3</name></user> 
      </group> 
      <user><name>user4</name></user> 
    </users>').extract('/*//*[text()]'))) t; 

我想有這樣的結果:

path      value 
------------------------ -------------- 
/users/user/name   user1 
/users/user/name   user2 
/users/group/user/name user3 
/users/user/name   user4 

我看不出去解決這個問題。我想有兩個東西要一起正常工作:

  • 我可以提取一個XMLTypepath單個操作或方法,還是我用繩子魔術做到這一點?
  • 什麼是正確的XPath表達式,以便我得到整個元素路徑(如果可能的話),例如。 <users><group><user><name>user3</name></user></group></user> insead的<user><name>user3</name></user>

也許我完全不理解XMLType。這可能是我需要一種不同的方法,但我看不到它。

圖片的標題說明:

  • 在最終版本的XML文檔會被從一個表,而不是一個靜態文件的CLOB到來。
  • path列當然也可以使用點或任何和初始斜槓不是問題,任何表示會做。
  • 此外,我不介意每個內部節點是否也得到結果行(可能與nullvalue),而不僅僅是其中的text()(這是我真正感興趣的)。
  • 最後,我將需要尾部元件的path分開(在這裏的例子中總是"name",但是這將在後面有所不同),即('/users/groups/user', 'name', 'user3'),我可以分別處理這一點。

回答

4

可以實現與XMLTable功能的幫助下Oracle XML DB XQuery function set

select * from 
    XMLTable(
    ' 
    declare function local:path-to-node($nodes as node()*) as xs:string* { 
     $nodes/string-join(ancestor-or-self::*/name(.), ''/'') 
    }; 
    for $i in $rdoc//name 
     return <ret><name_path>{local:path-to-node($i)}</name_path>{$i}</ret> 
    ' 
    passing 
    XMLParse(content ' 
     <users><user><name>user1</name></user> 
      <user><name>user2</name></user> 
      <group> 
       <user><name>user3</name></user> 
      </group> 
      <user><name>user4</name></user> 
     </users>' 
    ) 
    as "rdoc" 
    columns 
     name_path varchar2(4000) path '//ret/name_path', 
     name_value varchar2(4000) path '//ret/name' 

) 

對我來說XQuery看起來至少比XSLT更直觀的XML數據操作。您可以找到有用的一組XQuery函數here

更新1

我想你需要在最後階段完全的數據完全純數據集。 這個目標可以通過複雜的方式來達成,下面逐步構建,但是這個變體非常資源憤怒。我建議審查最終目標(選擇一些特定記錄,計算元素數量等),然後簡化該解決方案或完全改變它。

更新2個

從這個更新中刪除除最後所有的步驟,因爲提出的意見更優雅的解決方案@ A.B.Cade。 此解決方案提供於更新3部分下方。

步驟1 - 構建的ID的數據集對應的查詢結果

步驟2 - 彙總到一個XML列

步驟3 - 最後得到充分純通過使用XMLTable查詢約束XML來創建數據集

with xmlsource as (
    -- only for purpose to write long string only once 
    select ' 
     <users><user><name>user1</name></user> 
      <user><name>user2</name></user> 
      <group> 
       <user><name>user3</name></user> 
      </group> 
      <user><name>user4</name></user> 
     </users>' xml_string 
    from dual 
), 
xml_table as ( 
    -- model of xmltable 
    select 10 id, xml_string xml_data from xmlsource union all 
    select 20 id, xml_string xml_data from xmlsource union all 
    select 30 id, xml_string xml_data from xmlsource 
) 
select * 
from 
    XMLTable(
    ' 
     for $entry_user in $full_doc/full_list/list_entry/name_info 
      return <tuple> 
        <id>{data($entry_user/../@id_value)}</id> 
        <path>{$entry_user/name_path/text()}</path> 
        <name>{$entry_user/name_value/text()}</name> 
        </tuple> 
    ' 
    passing ( 
     select 
     XMLElement("full_list", 
      XMLAgg(  
      XMLElement("list_entry", 
       XMLAttributes(id as "id_value"), 
       XMLQuery(
       ' 
       declare function local:path-to-node($nodes as node()*) as xs:string* { 
        $nodes/string-join(ancestor-or-self::*/name(.), ''/'') 
       };(: function to construct path :) 
       for $i in $rdoc//name return <name_info><name_path>{local:path-to-node($i)}</name_path><name_value>{$i/text()}</name_value></name_info> 
       ' 
       passing by value XMLParse(content xml_data) as "rdoc" 
       returning content 
      ) 
      ) 
     ) 
     )   
     from xml_table 
    ) 
    as "full_doc"  
    columns 
     id_val varchar2(4000) path '//tuple/id', 
     path_val varchar2(4000) path '//tuple/path', 
     name_val varchar2(4000) path '//tuple/name' 
)  

更新3

正如在他的評論中提到的@ A.B.Cade,有非常簡單的方式加入ID的使用XQuery結果。

因爲我不喜歡回答的外部鏈接,下面的代碼表示his SQL fiddle,從這個答案適用於數據源一點點:

with xmlsource as (
    -- only for purpose to write long string only once 
    select ' 
     <users><user><name>user1</name></user> 
      <user><name>user2</name></user> 
      <group> 
       <user><name>user3</name></user> 
      </group> 
      <user><name>user4</name></user> 
     </users>' xml_string 
    from dual 
), 
xml_table as ( 
    -- model of xmltable 
    select 10 id, xml_string xml_data from xmlsource union all 
    select 20 id, xml_string xml_data from xmlsource union all 
    select 30 id, xml_string xml_data from xmlsource 
) 
select xd.id, x.* from 
xml_table xd, 
    XMLTable(
    'declare function local:path-to-node($nodes as node()*) as xs:string* {$nodes/string-join(ancestor-or-self::*/name(.), ''/'')  };  for $i in $rdoc//name  return <ret><name_path>{local:path-to-node($i)}</name_path>{$i}</ret> ' 
    passing 
    XMLParse(content xd.xml_data 
    ) 
    as "rdoc" 
    columns 
     name_path varchar2(4000) path '//ret/name_path', 
     name_value varchar2(4000) path '//ret/name' 

) x 
+0

優秀!有了這個和一些自己的修改,我幾乎在那裏。我現在只需要將實際表的CLOB列而不是文字Xml-String傳遞給'XMLParse(content'...')'。讓表的名稱爲「xmldata」,CLOB列爲「xml」。如何把它放在那裏?在某種程度上,結果將是一個循環內部的循環。所以我不能僅僅寫'xmldata.xml'來代替''...'-literal ... – towi 2013-04-09 12:41:39

+1

爲什麼不能像這樣http://www.sqlfiddle.com/#!4/beba9/2 – 2013-04-09 15:00:35

+0

@ABCade因爲我不太熟悉包含從當前行構建的數據集的連接(即使使用多集功能和嵌套表)。這似乎是一個非常強大的功能,但用於具有很長髮展歷史的生產系統,我很少使用它。謝謝你的正確回答,這對我也是有用的,不僅僅是對於這個問題的作者。 – ThinkJet 2013-04-09 22:40:26

2

這不是完美的,但可能是一個開始:

Here is a sqlfiddle

with xslt as (
    select '<?xml version="1.0" ?><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:template match="/"> 
    <records> 
    <xsl:apply-templates/> 
    </records> 
    </xsl:template> 
    <xsl:template match="//name"> 
     <columns> 
     <path> 
     <xsl:for-each select="ancestor-or-self::*"> 
      <xsl:call-template name="print-step"/> 
     </xsl:for-each> 
     </path> 
    <value> 
    <xsl:value-of select="."/> 
    </value> 
     <xsl:apply-templates select="*"/> 
     </columns> 
    </xsl:template> 
    <xsl:template name="print-step"> 
     <xsl:text>/</xsl:text> 
     <xsl:value-of select="name()"/> 
     <xsl:text>[</xsl:text> 
     <xsl:value-of select="1+count(preceding-sibling::*)"/> 
     <xsl:text>]</xsl:text> 
    </xsl:template> 
    </xsl:stylesheet>' 
    xsl from dual) 
, xmldata as 
(select xmltransform(xmltype('<?xml version="1.0"?> 
    <users><user><name>user1</name></user> 
      <user><name>user2</name></user> 
      <group> 
       <user><name>user3</name></user> 
      </group> 
      <user><name>user4</name></user> 
    </users>'), xmltype(xsl)) xd from xslt) 

select XT.* 
from xmldata c, 
xmltable('$x//columns' passing c.xd 
    as "x" 
     columns 
     path_c VARCHAR2(4000) PATH 'path', 
     value_c VARCHAR2(4000) PATH 'value' 
     ) as XT 

這就是我試圖做的:

既然你想要的「路徑」,我不得不使用xslt(credits to this post

然後我用xmltransform轉換你的orig與XSL到 所需的輸出(路徑,值)伊納勒XML

然後我用xmltable讀它作爲表

+0

哇!我不會想到xslt轉換。我必須考慮這一點。無論如何,感謝「不完全不復雜」的樣式表。 ;-)和'Xmltable',我也會查找。 – towi 2013-04-09 09:05:20

1

這上面的回答改善對ABCade:

<xsl:template name="print-step"> 
    <xsl:variable name="name" select="name()" /> 
    <xsl:text>/</xsl:text> 
    <xsl:value-of select="$name"/> 
    <xsl:text>[</xsl:text> 
    <xsl:value-of select="1+count(preceding-sibling::*[name()=$name])"/> 
    <xsl:text>]</xsl:text> 
</xsl:template> 

隨着結果:

/用戶[1] /用戶[1] /名稱[1] USER1

/用戶[1] /用戶[2] /名稱[1] USER2

/用戶[1] /組[1] /用戶[1] /名稱[1]用戶3

/使用者[ 1] /用戶[3] /名稱[1] USER4

代替:

/用戶[1] /用戶[1] /名稱[1] USER1

/用戶[1]/user [2]/name [1] user2

/users [1]/group [3]/user [1]/name [1] user3

/users [1]/user [4]/name [1] user4