XQuery tumbling window如何按首组起始项正确分组排除前缀误匹配
问题描述
在BaseX 9.7.3环境中,已通过tumbling window子句生成了排序后的名称列表,原始数据片段如下:
<data> <group> <key id="0c7b0bca-0349-489c-b45f-2612f3134a76">ovid</key> <key id="f77ab9c2-0be3-4348-809d-ab245e630f81">ovid 43 b c-17 or 18 a d</key> </group> <group> <key id="39b9d6c2-85a5-4c72-a83e-2a52e548fc3b">ovid 43 bc</key> <key id="acf5b3c0-8fd4-4e0c-950b-a40683bab431">ovid 43 bc-17 ad</key> <key id="cc57be53-9ca8-4b5e-97cf-1aeca798cded">ovid 43 bc-17 ad or 18 a</key> <key id="8395e750-1e52-4152-9d37-8c8f4e389fd3">ovid 43 bc-17 ad or 18 ad</key> </group> <group> <key id="0be07fc6-d9bf-4d56-8352-1885b4dd6574">ovid 43 bc-17 or 18</key> <key id="e3aafc69-56b0-4632-a96c-26ca448c6c2d">ovid 43 bc-17 or 18 ad</key> </group> <group> <key id="f9615365-4a32-442b-9e20-9c5abb0e6fa0">ovide</key> <key id="c7b45a8d-79a3-4e79-b32b-8d918f67a7b0">ovide 0043 av j-c-0017</key> </group> </data>
需要对该数据做进一步分组:所有ovid开头的条目归为第一组(从"ovid"开始,到"ovid 43 bc-17 or 18 ad"结束),ovide开头的条目单独分为第二组,期望输出结构如下:
<data> <group> <key id="0c7b0bca-0349-489c-b45f-2612f3134a76">ovid</key> <key id="f77ab9c2-0be3-4348-809d-ab245e630f81">ovid 43 b c-17 or 18 a d</key> <key id="39b9d6c2-85a5-4c72-a83e-2a52e548fc3b">ovid 43 bc</key> <key id="acf5b3c0-8fd4-4e0c-950b-a40683bab431">ovid 43 bc-17 ad</key> <key id="cc57be53-9ca8-4b5e-97cf-1aeca798cded">ovid 43 bc-17 ad or 18 a</key> <key id="8395e750-1e52-4152-9d37-8c8f4e389fd3">ovid 43 bc-17 ad or 18 ad</key> <key id="0be07fc6-d9bf-4d56-8352-1885b4dd6574">ovid 43 bc-17 or 18</key> <key id="e3aafc69-56b0-4632-a96c-26ca448c6c2d">ovid 43 bc-17 or 18 ad</key> </group> <group> <key id="f9615365-4a32-442b-9e20-9c5abb0e6fa0">ovide</key> <key id="c7b45a8d-79a3-4e79-b32b-8d918f67a7b0">ovide 0043 av j-c-0017</key> </group> </data>
当前编写的查询语句执行后仅复现原输入文档,无法实现预期分组效果,原有错误代码如下:
<data>{ for tumbling window $entry in /*/group/key start $s at $sp previous $sprev next $snext when starts-with($snext, $s) end $e at $ep next $enext when not(starts-with($enext, $e)) return <group>{ for $k in $entry return ( <key id="{$k/@id}">{data($k)}</key> ) }</group> }</data>
核心诉求:是否可以通过对比窗口起始项的完整词元,匹配所有以该完整词元开头的后续条目完成分组,排除ovide这类字符串前缀部分匹配但不属于同组的条目?
解决方案
原有逻辑存在两个核心问题:
- 窗口起止判断逻辑错误,现有写法拿相邻项做前缀匹配,没有锚定每个分组的根词元,窗口边界判断完全失效
- 前缀匹配没有做词边界校验,
starts-with("ovide", "ovid")会返回true,直接导致不同根词的条目被错误归为同组
可以直接使用以下查询实现需求,核心逻辑是提取每个key的首个完整词元作为分组锚点,再做窗口聚合:
<data>{ let $all-keys := /*/group/key for tumbling window $entry in $all-keys start $s when true() end $e next $n when not($n) or tokenize($n, '\s+')[1] != tokenize($s, '\s+')[1] return <group>{ $entry/<key id="{@id}">{data(.)}</key> }</group> }</data>
逻辑说明:
- 用
tokenize(内容, '\s+')[1]提取每个key的首个完整词元,彻底避免ovid和ovide的误匹配 - 窗口从第一个条目自动启动,只要下一个条目的首词元和当前组起始项的首词元一致,就持续归入当前组,不一致就结束当前窗口开启新窗口
- 到序列最后一个条目时自动收尾,不会漏项
如果需要适配更多分隔符场景(比如根词元后接横杠、特殊符号等),可以把结束判断替换为正则匹配规则,兼容性更强:
end $e next $n when not($n) or not(matches($n, '^' || quote:regex-escape(tokenize($s, '\s+')[1]) || '(\s|$)'))
内容的提问来源于stack exchange,提问作者tat
相关产品推荐
相关产品推荐

