以下是我正在处理的问题的简化版本:我有一堆xml数据可以编码有关人员的信息。每个人都由'id'属性唯一标识,但它们可能有许多名称。例如,在一个文档中,我可能会找到
<person id=1>Paul Mcartney</person>
<person id=2>Ringo Starr</person>
在另一个我可能会发现:
<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的物理内存。
所以这就是我真的喜欢这样做(在Pythonic伪代码中):
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 :(得分:4)
遗憾的是,这是XQuery 1.0中的一个缺点
XQuery 1.1将group 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并没有被广泛实现,所以目前你没有使用group by子句。
作为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 n ,而不是您遇到的二次方。
不幸的是,虽然在组中添加了一组人员的明确值(以过滤掉重复的名称)似乎阻止XQSharp找到连接;这已被提交为一个错误。现在,这可以通过两次传递进行查询来解决 - 通过id对名称进行分组,并删除重复的名称。
总之,在XQuery 1.0中没有更好的方法,但是一些实现(例如XQSharp)将能够有效地评估它。如有疑问,请查看查询计划。
有关XQSharp执行的连接优化的更详细信息,请查看此blog post。
答案 1 :(得分: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>
答案 2 :(得分:1)
失败了,有什么东西 比我可能会使用的xquery更好 完成我的目标?因为真的, 我是计算资源 扔这个比较简单 问题有点荒谬。
这是一个简单的XSLT 2.0解决方案(为方便起见,三个文档中的两个由<xsl:variable>
s表示):
<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>
答案 3 :(得分: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个节点。
请注意,eXist中XQuery的更新扩展与XQuery Update语法的语法不完全相同