如何使用ElasticSearch与Searchkick按搜索词匹配词数排序查询结果
问题解答
Elasticsearch 原生完全支持按搜索词匹配数量调整结果优先级,你当前出现排序异常的核心原因是手动指定了固定的字段排序规则,完全覆盖了Elasticsearch默认的相关性算分排序逻辑,才会出现仅匹配单个词的结果排在多词匹配结果前的问题。
核心问题说明
你原有查询中写死了排序规则 order: {application_name: :asc, name: :asc},这个规则会强制所有结果严格按照应用名、名称的字符顺序排列,完全不考虑文档和搜索词的匹配程度,不管匹配了1个词还是全部搜索词,都按字段值排序,自然会出现不符合预期的排序结果。
实现方案
方案1:基础修复(绝大多数场景适用)
Elasticsearch默认的BM25相关性算法本身就会为匹配更多搜索词的文档计算更高的相关性得分,你只需要调整排序优先级,把相关性得分作为第一排序维度,原有字段排序作为同分结果的兜底规则即可。
修改后的查询代码如下:
results = Runnable.search(search_term, fields: ["application_name^10", "name^5"], match: :word_middle, # 优先按匹配相关性倒序排列,同得分下再按字段升序兜底 order: [ { _score: :desc }, { application_name: :asc }, { name: :asc } ], operator: "or", misspellings: false )
这个改法性能最好,不需要额外的自定义计算,改完之后匹配2个搜索词的结果会天然排在只匹配1个搜索词的结果前面,同时保留你之前设置的字段权重(application_name的匹配权重是name字段的2倍)。
方案2:强匹配词数优先级(特殊刚性需求适用)
如果你的业务要求匹配词数的优先级绝对高于字段权重(比如匹配2个词的name字段结果,必须排在只匹配1个词的高权重application_name字段结果前面),可以通过自定义脚本得分,显式将匹配词数作为最高权重因子加入算分逻辑,代码示例:
# 拆分用户输入的多词搜索关键词 search_keywords = search_term.strip.split(/\s+/) results = Runnable.search(search_term, fields: ["application_name^10", "name^5"], match: :word_middle, order: [{ _score: :desc }, { application_name: :asc }, { name: :asc }], operator: "or", misspellings: false, # 自定义算分函数,按匹配词数加权 functions: [ { script_score: { script: { source: """ int matched_word_count = 0; // 遍历所有搜索词,统计当前文档匹配到的词数量 for (keyword in params.keywords) { if (doc['application_name'].value.contains(keyword) || doc['name'].value.contains(keyword)) { matched_word_count++; } } // 匹配词数按指数级加权,确保匹配词多的结果得分绝对高于少词匹配结果 return _score * Math.pow(100, matched_word_count); """, params: { keywords: search_keywords } } } } ] )
注意事项
- 不建议直接将
operator参数修改为"and",该配置会要求文档必须匹配所有搜索词才能被召回,用户输入较长关键词时很容易出现无结果的问题,搜索容错性极差。 - 方案2的脚本适配你当前使用的
:word_middle中间模糊匹配模式,如果后续切换为标准分词匹配,可以直接使用Elasticsearch内置的词频统计字段替换字符串包含判断,查询性能会更高。
内容的提问来源于stack exchange,提问作者Sofronia
相关产品推荐
相关产品推荐

