如何在Solr 7.2中实现多搜索词全匹配加分、部分匹配减分?
嘿,这个问题我之前切换Solr版本的时候也踩过坑!Solr 7.x用的BM25确实抛弃了ClassicSimilarity里的coord函数,不过咱们有几种实用的办法能实现你要的「全匹配文档加分、部分匹配文档相对降权」的效果,给你捋一捋:
如果你用的是EDisMax查询解析器,直接用bq参数给同时匹配所有搜索词的文档加额外权重就行,完全不用改代码。
比如搜索词是「foo bar」,你可以这么写查询:
q=foo bar&defType=edismax&bq=(foo AND bar)^2.0
这里的bq是Boost Query,意思是同时包含foo和bar的文档,额外乘以2.0的权重,这样全匹配的文档评分会更高,自然排在部分匹配的前面。
优点:零开发成本,随查随用;缺点是如果搜索词是动态生成的(比如用户输入多个词),你需要动态构造(term1 AND term2 AND ...)这样的boost query语句。
如果需要更细粒度的控制(比如匹配2个词加X分,匹配1个词加Y分),可以用Solr的Function Query来统计匹配的词项数量,然后加到主评分里。
比如还是「foo bar」的场景,用termfreq函数统计每个文档中foo和bar的出现次数,然后求和作为加分项:
q={!boost b='sum(termfreq(content, "foo"), termfreq(content, "bar"))'}foo bar
这里的content是你的字段名,全匹配的文档会拿到2分的额外加分,部分匹配的拿1分,相当于间接实现了对部分匹配的「惩罚」。
如果你想给全匹配的文档加更多权重,还可以用if函数做条件判断:
q={!boost b='if(sum(termfreq(content,"foo"),termfreq(content,"bar"))=2, 1.5, 1)'}foo bar
意思是:匹配2个词的文档评分乘以1.5,匹配1个的乘以1,这样全匹配的文档评分会比部分匹配的高50%。
如果你希望整个索引的评分规则都统一处理这种全匹配加分的逻辑,那可以自定义一个继承自BM25Similarity的类,在评分过程中加入匹配词数的判断。
比如写一个Java类:
import org.apache.lucene.search.similarities.BM25Similarity; import org.apache.lucene.search.similarities.BasicStats; public class FullMatchBoostBM25 extends BM25Similarity { // 全匹配的额外权重系数 private final float fullMatchMultiplier = 1.5f; @Override public float score(BasicStats stats, float freq, float docLen) { float baseScore = super.score(stats, freq, docLen); // 这里需要获取当前文档匹配的搜索词数量,你可以通过自定义Weight或SimScorer来传递这个信息 // 简化实现的话,也可以在查询时把总词数传给Similarity,或者通过其他上下文获取 // 假设我们已经拿到了matchedTerms(匹配的词数) int matchedTerms = getMatchedTermsCount(); if (matchedTerms == 2) { // 这里的2对应搜索词的数量,实际可以动态调整 return baseScore * fullMatchMultiplier; } return baseScore; } // 自定义方法,用于获取当前文档匹配的词项数量(需要结合Lucene的查询上下文实现) private int getMatchedTermsCount() { // 具体实现需要结合你的查询逻辑,比如从Weight的score方法中传递参数 // 这里只是示例,实际开发中需要完善 return 0; } }
把这个类打包成jar,放到Solr的lib目录下,然后在schema.xml里配置使用这个自定义的Similarity:
<similarity class="com.yourcompany.solr.FullMatchBoostBM25"/>
优点:全局生效,不需要每次查询都加额外参数;缺点是需要开发和部署代码,维护成本稍高。
内容的提问来源于stack exchange,提问作者zelinka

