You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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版本中测试过,请问哪里出错了?该如何解决?


错误原因

  1. 嵌套查询得分逻辑缺陷:默认nested查询只会计算第一个匹配的嵌套文档的得分,不会累加所有匹配项的得分。所以Michael虽然有3次beta匹配,得分却和其他仅2次匹配的文档持平,甚至因其他隐性因素排后。
  2. function_score位置错误:你把function_score放在了nested的query内部,这只会对单个嵌套文档计算得分,无法将所有匹配的嵌套文档得分聚合到根文档。
  3. 日期范围逻辑颠倒: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" // 将基础查询得分(工具次数)和时间权重得分相加
    }
  }
}

关键调整说明

  1. 外层用function_score:把nested查询作为基础查询,确保根文档能聚合所有匹配嵌套文档的得分。
  2. nested查询设置score_mode: sum:让根文档得分等于所有匹配嵌套文档的得分之和,工具出现次数越多,得分越高。
  3. 修正日期区间:将反向的日期范围改为正向,比如最近1年设为gte: now-1y, lt: now。
  4. 每个时间权重单独做嵌套过滤:在function中嵌套查询,仅对包含目标工具且在对应时间范围内的嵌套文档计算权重,再通过score_mode: sum累加符合条件的次数得分。
  5. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:07:10