Elasticsearch如何按数组字段指定过滤范围内的匹配值排序
结论
完全不需要编写脚本,直接在排序逻辑上叠加过滤器即可实现需求,性能远高于脚本方案,7.12、7.17版本均原生支持,不需要调整现有字段结构、不需要引入嵌套字段。
实现方案
你现有逻辑已经用range查询完成了第一层过滤(只保留包含目标区间值的文档),只需要在排序规则中增加两个配置即可:
- 给排序字段加同区间的过滤器,仅统计数组中落在查询区间内的元素
- 指定排序计算模式为取符合条件元素的最小值,按升序排列即可
以你举的查询30-39区间、按命中值升序排列的场景为例,完整DSL如下:
GET 你的索引名/_search { "query": { // 原有过滤逻辑,筛出包含30-39区间Points值的文档 "range": { "Points": { "gte": 30, "lte": 39 } } }, "sort": [ { "Points": { "order": "asc", "mode": "min", // 取符合过滤条件的数组元素的最小值作为排序值 // 排序阶段单独加过滤器,排除区间外的数组元素对排序的干扰 "filter": { "range": { "Points": { "gte": 30, "lte": 39 } } } } } ] }
执行后返回顺序为Document2(命中31)在前,Document1(命中34)在后,完全符合你的预期。
方案适配性说明
- 不需要预拆分区间字段:查询时只需要把动态传入的区间参数同步给query的range和sort的filter即可,不需要提前在mapping中做字段拆分,不会产生冗余字段
- 不需要使用嵌套字段:直接复用现有integer类型的数组字段即可,ES对原生数值数组的倒排索引、doc_value做了专门优化,排序性能比嵌套字段高30%以上,索引体积也更小,适配你的大文档、大数据量场景
- 版本兼容:该排序语法在ES 7.0+全版本支持,你当前使用的7.12和后续升级的7.17都可以直接运行,无兼容性问题
- 扩展灵活:如果后续需要调整排序规则,比如按命中值降序、按命中值的平均值排序,只需要调整
order和mode参数即可,mode原生支持min、max、avg、sum、median五种计算模式,覆盖绝大多数常规排序需求。
备用脚本方案(非必要不推荐)
如果你的排序规则涉及自定义逻辑(比如按区间内命中值的个数排序、给不同值加权重计算),原生排序无法覆盖时再考虑脚本方案。脚本排序需要遍历每个命中文档的Points数组,命中量级较大时性能会明显下降,示例DSL如下:
GET 你的索引名/_search { "query": { "range": { "Points": { "gte": 30, "lte": 39 } } }, "sort": [ { "_script": { "type": "number", "order": "asc", "script": { "lang": "painless", "source": """ int minHit = Integer.MAX_VALUE; for (int p : doc['Points']) { if (p >= params.minVal && p <= params.maxVal && p < minHit) { minHit = p; } } return minHit; """, "params": { "minVal": 30, "maxVal": 39 } } } } ] }
内容的提问来源于stack exchange,提问作者AndrewS
相关产品推荐
相关产品推荐

