2010-05-13 86 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>