ElasticSearch中嵌套对象结合function_score的用法及排序问题排查
问题背景
我创建了一个包含nested类型属性的ElasticSearch索引mycvs,结构如下:
PUT /mycvs { "mappings": { "properties": { "name": { "type": "text" }, "experiences": { "type": "nested", "properties": { "date": { "type": "date" }, "tools": { "type": "text" } } } } } }
插入的数据如下:
POST /mycvs/_doc { "name": "Michael", "experiences": [ { "date": "2023-03-13T19:50:14.820Z", "tools": ["alpha", "beta"] }, { "date": "2022-03-13T19:50:14.820Z", "tools": ["alpha", "beta"] }, { "date": "2021-03-13T19:50:14.820Z", "tools": ["beta", "gamma"] } ] } POST /mycvs/_doc { "name": "Pam", "experiences": [ { "date": "2023-03-13T19:50:14.820Z", "tools": ["beta"] }, { "date": "2020-03-13T19:50:14.820Z", "tools": ["gamma"] }, { "date": "2019-03-13T19:50:14.820Z", "tools": ["beta"] } ] } POST /mycvs/_doc { "name": "Dwight", "experiences": [ { "date": "2022-03-13T19:50:14.820Z", "tools": ["beta"] }, { "date": "2021-03-13T19:50:14.820Z", "tools": ["gamma", "beta"] }, { "date": "2021-03-13T19:50:14.820Z", "tools": ["gamma"] } ] }
执行以下查询时,返回全部3条数据,但包含3次beta的Michael排在最后,我希望他能排在首位:
GET /mycvs/_search { "query": { "nested": { "path": "experiences", "query": { "match_phrase": { "experiences.tools": { "query": "beta" } } } } } }
我的最终目标是:筛选出包含指定工具的简历,按工具出现次数排序,并根据经验的时间远近提升得分。尝试了以下function_score查询但未生效:
GET /mycvs/_search { "query": { "nested": { "path": "experiences", "query": { "function_score": { "query": { "match_phrase": { "experiences.tools": { "query": "beta" } } }, "score_mode": "multiply", "functions": [ { "filter": { "range": { "experiences.date": { "gte": "now", "lt": "now-1y" } } }, "weight": 5 }, { "filter": { "range": { "experiences.date": { "gte": "now-1y", "lt": "now-2y" } } }, "weight": 4 }, { "filter": { "range": { "experiences.date": { "gte": "now-2y", "lt": "now-3y" } } }, "weight": 3 }, { "filter": { "range": { "experiences.date": { "gte": "now-3y", "lt": "now-4y" } } }, "weight": 2 }, { "filter": { "range": { "experiences.date": { "gte": "now-4y", "lt": "now-5y" } } }, "weight": 1 }, { "filter": { "range": { "experiences.date": { "gte": "now-5y" } } }, "weight": 1 } ] } } } } }
我在ElasticSearch 7.17.0和8.6.2版本中测试过,请问哪里出错了?该如何解决?
错误原因
- 嵌套查询得分逻辑缺陷:默认nested查询只会计算第一个匹配的嵌套文档的得分,不会累加所有匹配项的得分。所以Michael虽然有3次beta匹配,得分却和其他仅2次匹配的文档持平,甚至因其他隐性因素排后。
- function_score位置错误:你把function_score放在了nested的query内部,这只会对单个嵌套文档计算得分,无法将所有匹配的嵌套文档得分聚合到根文档。
- 日期范围逻辑颠倒:
gte: "now", lt: "now-1y"是反向区间(now比now-1y时间更晚),不会匹配任何数据,应该调整为gte: "now-1y", lt: "now"这类正向区间。
解决方案
要实现工具出现次数+时间权重排序,需要将nested查询与function_score结合,通过score_mode和boost_mode控制得分聚合,同时修正日期逻辑。
正确查询语句
GET /mycvs/_search { "query": { "function_score": { "query": { "nested": { "path": "experiences", "query": { "match_phrase": { "experiences.tools": "beta" } }, "score_mode": "sum" // 累加所有匹配嵌套文档的得分,体现工具出现次数 } }, "functions": [ { "nested": { "path": "experiences", "filter": { "bool": { "must": [ { "match_phrase": { "experiences.tools": "beta" } }, { "range": { "experiences.date": { "gte": "now-1y", "lt": "now" } } } ] } }, "score_mode": "sum", "weight": 5 } }, { "nested": { "path": "experiences", "filter": { "bool": { "must": [ { "match_phrase": { "experiences.tools": "beta" } }, { "range": { "experiences.date": { "gte": "now-2y", "lt": "now-1y" } } } ] } }, "score_mode": "sum", "weight": 4 } }, { "nested": { "path": "experiences", "filter": { "bool": { "must": [ { "match_phrase": { "experiences.tools": "beta" } }, { "range": { "experiences.date": { "gte": "now-3y", "lt": "now-2y" } } } ] } }, "score_mode": "sum", "weight": 3 } }, { "nested": { "path": "experiences", "filter": { "bool": { "must": [ { "match_phrase": { "experiences.tools": "beta" } }, { "range": { "experiences.date": { "gte": "now-4y", "lt": "now-3y" } } } ] } }, "score_mode": "sum", "weight": 2 } }, { "nested": { "path": "experiences", "filter": { "bool": { "must": [ { "match_phrase": { "experiences.tools": "beta" } }, { "range": { "experiences.date": { "gte": "now-5y", "lt": "now-4y" } } } ] } }, "score_mode": "sum", "weight": 1 } }, { "nested": { "path": "experiences", "filter": { "bool": { "must": [ { "match_phrase": { "experiences.tools": "beta" } }, { "range": { "experiences.date": { "lt": "now-5y" } } } ] } }, "score_mode": "sum", "weight": 1 } } ], "score_mode": "sum", // 累加所有时间权重的得分 "boost_mode": "sum" // 将基础查询得分(工具次数)和时间权重得分相加 } } }
关键调整说明
- 外层用function_score:把nested查询作为基础查询,确保根文档能聚合所有匹配嵌套文档的得分。
- nested查询设置
score_mode: sum:让根文档得分等于所有匹配嵌套文档的得分之和,工具出现次数越多,得分越高。 - 修正日期区间:将反向的日期范围改为正向,比如最近1年设为
gte: now-1y, lt: now。 - 每个时间权重单独做嵌套过滤:在function中嵌套查询,仅对包含目标工具且在对应时间范围内的嵌套文档计算权重,再通过
score_mode: sum累加符合条件的次数得分。 score_mode和boost_mode设为sum:将时间权重得分与基础查询得分相加,最终得分同时体现工具出现次数和时间优先级。
简化版本(可选)
如果不需要阶梯式权重,希望时间越近得分平滑衰减,可以用gauss函数实现:
GET /mycvs/_search { "query": { "function_score": { "query": { "nested": { "path": "experiences", "query": { "match_phrase": { "experiences.tools": "beta" } }, "score_mode": "sum" } }, "functions": [ { "nested": { "path": "experiences", "filter": { "match_phrase": { "experiences.tools": "beta" } }, "score_mode": "sum", "function": { "gauss": { "experiences.date": { "origin": "now", "scale": "1y", "offset": "0d", "decay": 0.5 } } } } } ], "score_mode": "sum", "boost_mode": "sum" } } }
这个版本中,最近1年的经验得分最高,每过1年得分衰减50%,同时累加所有匹配工具的次数得分。
内容的提问来源于stack exchange,提问作者Kirian CAUMES
相关产品推荐
相关产品推荐

