与许多人一样,我正在处理关于XML的Mondial数据库。如果XQuery语法没有尽力破坏它,那将是一块蛋糕。
let $inland := //province/@id
where every $sea in //sea satisfies
$sea/located/@province != $inland
return $inland
我在上面尝试做的是找到所有"内陆"各省,不的省份旁边都有海。然而,这并不起作用,因为$ sea / located / province是一个大字符串,每个省都与它接壤。
所以我试着修改成。
let $inland := //province/@id
where every $sea in //sea satisfies
not(contains($sea/located/@province, $inland))
return $inland
我希望它只能找到属于海洋边界省份的省份。简单明了。
错误讯息:
Stopped at C:/Users/saffekaffe/Desktop/mondial/xml/country_without_island.xml, 2/1:
[XPTY0004] Item expected, sequence found: (attribute id {"prov-Greece-2"},....
我该如何解决这个问题?
// sea / located / province @
的示例province="prov-France-5 prov-France-20 prov-France-89 prov-France-99"
//省/ @ id
的示例id="prov-Greece-2"
答案 0 :(得分:4)
XQuery有多种方式以不同于您期望的方式工作。
比较运算符=
和!=
具有存在语义,如果它们的至少一个参数是序列而不是单项。这意味着$seq1 = $seq2
相当于some $x in $seq1, $y in $seq2 satisfies $x = $y
。查询('foo', 'bar') = ('bar', 'baz', 'quuz')
会返回true
,因为至少有一个公共项目。
像//province/@id
这样的XQuery异常会评估所有匹配节点的序列。在您的情况下,这将是超过1000个省ID的序列:(id="prov-cid-cia-Greece-2", id="prov-cid-cia-Greece-3", id="prov-cid-cia-Greece-4", [...])
。然后将此序列绑定到$inland
子句中的变量let
。由于您没有迭代$inland
中的单个项目(例如使用for
子句),因此where
条件可以同时适用于全球所有省份的整个序列。所以你的条件every $sea in //sea satisfies
$sea/located/@province != $inland
现在意味着:
“对于每个sea
,其旁边都有一个province
,@id
的{{1}}不等于至少一个现有的省ID 。”
Th返回false
,因为sea
个没有located
个孩子,例如亚丁湾。
contains($str, $sub)
不适合检查子字符串是否包含在以空格分隔的字符串中,因为它还匹配部分条目:contains("foobar baz quux", "oob")
返回true
。
相反,您应该使用tokenize($str)
将字符串拆分为其部分并查看其部分,或使用contains-token($str, $token)
。
总而言之,正确的查询非常类似于原始查询:
for $inland in //province/@id
where
every $sea in //sea
satisfies not(contains-token($sea/located/@province, $inland))
return $inland
另一种方法是首先收集sea
旁边的所有(唯一)省份,然后返回不在该序列中的所有省份:
let $next-to-sea := distinct-values(//sea/located/@province/tokenize(.))
return //province/@id[not(. = $next-to-sea)]
更紧凑(但效率可能更低):
//province/@id[not(. = //sea/located/@province/tokenize(.))]
另一方面,您可以使用XQuery 3.0 地图通过单一查找替换所有海边省份的潜在线性搜索:
let $seaside :=
map:merge(
for $id in //sea/located/@province/tokenize(.)
return map{ $id: () }
)
return //province/@id[not(map:contains($seaside, .))]