2010-05-13 82 views
3

下面是我正在處理的問題的簡化版本:我有一堆xml數據,用於編碼有關人員的信息。每個人都有唯一的'id'屬性,但他們可能會有很多名字。例如,在一個文檔中,我可能會發現編寫更高效的xquery代碼(避免冗餘迭代)

而在另一個我可能會發現:

<person id=1>Sir Paul McCartney</person> 
<person id=2>Richard Starkey</person> 

我想使用XQuery產生一個新文檔,列出與給定ID相關聯的每一個名字。即:

<person id=1> 
    <name>Paul McCartney</name> 
    <name>Sir Paul McCartney</name> 
    <name>James Paul McCartney</name> 
</person> 
<person id=2> 
    ... 
</person> 

我在XQuery目前這樣做的方式是這樣的(僞代碼式的):

let $ids := distinct-terms([all the id attributes on people]) 
for $id in $ids 
    return <person id={$id}> 
    { 
    for $unique-name in distinct-values 
      (
      for $name in ([all names]) 
      where $name/@id=$id 
      return $name 
      ) 
     return <name>{$unique-name}</name> 
    } 
    </person> 

的問題是,這實在是太慢了。我想象的瓶頸是最內層的循環,每個id(其中大約有1200個)會執行一次。我正在處理一些公平的數據(300 MB,分佈在大約800個xml文件中),因此即使在內部循環中執行一次查詢也需要大約12秒,這意味着重複1200次需要大約4次小時(這可能是樂觀的 - 該過程至今已經運行了3個小時)。它不僅速度慢,而且使用了大量的虛擬內存。我使用的是Saxon,爲了避免出現內存錯誤,我必須將java的最大堆大小設置爲10 GB(!),並且它目前使用6 GB的物理內存。

因此,這裏就是我真的很想這樣做(在Python化僞代碼):

persons = {} 
for id in ids: 
    person[id] = set() 
for person in all_the_people_in_my_xml_document: 
    persons[person.id].add(person.name) 

在那裏,我只是做了它的線性時間,只有一次掃描的XML文檔。現在,有沒有辦法在xquery中做類似的事情?當然,如果我能想象得到它,一種合理的編程語言應該能夠做到這一點(他說,混沌地說)。我想這個問題是,與Python不同,xquery沒有(據我所知)具有像關聯數組一樣的東西。

有沒有一些聰明的方法呢?如果不這樣做,是否有什麼比我用來實現目標的xquery更好?因爲真的,我投擲在這個相對簡單的問題上的計算資源有點荒謬。

+0

我覺得同樣的事情使用VTD-XML和XPath可以更快地完成,這是否會成爲您考慮的選項? – 2010-05-13 02:40:03

+0

好問題(+1)。查看我的答案以獲得簡單高效的XSLT 2.0解決方案。 – 2010-09-04 17:11:16

回答

4

這不幸的是,在XQuery的一個缺點1.0

的XQuery 1.1 by子句附加組的語法來解決這個問題,您的問題將與解決:

for $person in /person 
let $id = $person/@id 
group by $id 
return <people id="{$id}">{ 
      for $name in distinct-values($person) 
      return <name>{$name}</name> 
     }</people> 

不幸的XQuery 1.1沒有廣泛實施,所以目前你沒有分組條款。

作爲XQSharp的開發人員,我不能說任何其他的實現,但我們花了很多時間調整我們的優化器,以發現XQuery 1.1中的常見分組模式,並使用您指定的算法執行它們。

特別是,查詢以下版本:

declare variable $people as element(person, xs:untyped)* external; 

for $id in distinct-values($people/@id) 
return <people id="{$id}">{ 
      for $person in $people 
      where $person/@id = $id 
      return <name>{$person}</name> 
     }</people> 

被發現爲一組,由,如通過下面的查詢計劃證明:

library http://www.w3.org/2005/xpath-functions external; 
library http://www.w3.org/2001/XMLSchema external; 
declare variable $people external; 

for $distinct-person in $people 
let $id := http://www.w3.org/2005/xpath-functions:data($distinct-person/attribute::id) 
group by 
    $id 
aggregate 
    element {name} { fs:item-sequence-to-node-sequence($distinct-person) } 
as 
    $:temp:19 
return 
    element {person} { (attribute {id} { $id } , fs:item-sequence-to-node-sequence($:temp:19)) } 

注意類型標註as element(person, xs:untyped)*是必需的,因爲不知道該節點是無類型(針對一個模式不進行驗證),查詢處理器無法知道$person/@id不具有在其數據值的多個項目的方法。 XQSharp還不支持group by表達式,其中每個節點可以有多個鍵。然而,在這種情況下,一個左外連接仍然發現,所以複雜性應大致的n logñ,當你遇到不二次。

雖然組(過濾掉重複的名字)似乎從發現的連接停止XQSharp將圍繞一組人的不同值不幸的是,這已被歸檔爲一個錯誤。通過ID分組的名稱,並刪除重複的名字 - 現在,這可以通過做查詢分兩次解決。總之,在XQuery 1.0中沒有更好的方法,但是一些實現(例如.XQSharp)將能夠有效地評估它。如果有疑問,請檢查查詢計劃。

對於在由XQSharp進行聯接優化更詳細的研究,看看這個blog post

+0

性能會很慢,因爲它是相當計算密集型的... – 2010-05-13 23:43:44

+0

謝謝。一個非常豐富的答案。我使用的是Saxon,其開源版本不包括對1.1的支持,所以我想用'group by'是不可能的。無論如何,我很高興知道我找不到一個有效的解決方案並不是因爲缺乏想象力而造成的。 我一定會看看XQSharp。我一直在嘗試的另一個選擇是編寫一個Python腳本,這樣我就可以將XPath(使用類似xml2的庫)與Python數據結構和控制流結合起來。 – Coquelicot 2010-05-14 08:33:59

0

如果您使用支持更新的XML數據庫,例如eXist db,那麼您可以像Pythonesque代碼那樣直接將分組進行到XML文檔中,這可能是後續處理所需的結果。

let $persons := doc("/db/temp/p3.xml")/persons 
let $person-groups := doc("/db/temp/p2.xml")/person-groups 
for $person in $persons/person 
let $name := element name {$person/text()} 
let $person-group := $person-groups/person-group[@id=$person/@id] 
return 
    if ($person-group) 
    then update insert $name into $person-group 
    else update insert element person-group {attribute id {$person/@id}, $name} 
     into $person-groups 

對於我在100個不同ID中的10,000個人節點的實驗,我們服務器上的eXist的吞吐量約爲每秒100個節點。

注意,更新擴展的XQuery中不存在都不太相同的語法XQuery更新語法

1

另一種選擇:使用地圖。

let $map := map:map() 
let $people := 
    for $person in $all-people 
    return map:put($map, $person/@id, 
    (map:get($map, $person/@id), <name>{$person/text()}</name>)) 
return 
    for $id in map:keys($map) 
    return 
    <person id="{$id}">{map:get($map, $id)}</person> 
1

做不到這一點,是有什麼 比XQuery的,我可能會使用到 完成我的目標是什麼?因爲真的, 的計算資源,我 在這種相對簡單的 問題拋是種荒謬的。

這是一個簡單的XSLT 2。0溶液(對於三個文件的方便的2個各自<xsl:variable>小號表示):

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 

<xsl:variable name="vDoc2"> 
    <persons> 
    <person id="1">Sir Paul McCartney</person> 
    <person id="2">Richard Starkey</person> 
    </persons> 
</xsl:variable> 

<xsl:variable name="vDoc3"> 
    <persons> 
    <person id="1">James Paul McCartney</person> 
    <person id="2">Richard Starkey - Ringo Starr</person> 
    </persons> 
</xsl:variable> 

<xsl:template match="/"> 
    <xsl:for-each-group group-by="@id" select= 
    "(/ | $vDoc2 | $vDoc3)/*/person"> 

    <person id="{current-grouping-key()}"> 
    <xsl:for-each select="current-group()"> 
     <name><xsl:sequence select="text()"/></name> 
    </xsl:for-each> 
    </person> 

    </xsl:for-each-group> 
</xsl:template> 
</xsl:stylesheet> 

當在下面的XML文檔施加這種轉變:

<persons> 
    <person id="1">Paul Mcartney</person> 
    <person id="2">Ringo Starr</person> 
</persons> 

有用,正確結果產生

<person id="1"> 
    <name>Paul Mcartney</name> 
    <name>Sir Paul McCartney</name> 
    <name>James Paul McCartney</name> 
</person> 
<person id="2"> 
    <name>Ringo Starr</name> 
    <name>Richard Starkey</name> 
    <name>Richard Starkey - Ringo Starr</name> 
</person>