## 排序
### 一、默认排序规则
默认情况下,是按照_score降序排序。
_score使用的算法,计算出一个索引中的文本,与搜索文本,他们之间的关联匹配程度
es使用的是,term frequency和inverse documnet frequency算法,简称为TF/IDF算法
term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,分数越高
inverse documnet frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,分数越低
从Elasticsearch 5之后, 缺省的打分机制改成了 __Okapi BM25__ 。
BM25 的 BM 是缩写自 Best Match, 25 貌似是经过 25 次迭代调整之后得出的算法,它也是基于 TF/IDF 进化来的。
Elasticsearch有三种控制相关度分数的方法:
- boost
- boosting
- function_score
评分公式
```
score(q,d) = queryNorm(q) //归一化因子
· coord(q,d) //协调因子
· ∑ (
tf(t in d) //词频
· idf(t)² //逆向文档频率
· t.getBoost() //权重
· norm(t,d) //字段长度归一值
) (t in q)
```
搜索时指定某一个字段,权重加大,当 boost > 1 时, 打分的权重相对性提升
```
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("documentTitle", "201文")).boost(2.0f);
```
boosting
```
QueryBuilders.boostingQuery(QueryBuilders.matchQuery("documentTitle", "201文"),
QueryBuilders.matchQuery("flag", "123")).negativeBoost(0.2f);
```
function_score
```
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery().add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("documentTitle", searchContent)),ScoreFunctionBuilders.weightFactorFunction(1000)).add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("description", searchContent)),
ScoreFunctionBuilders.weightFactorFunction(100));
```
### 二、自定义排序
要实现自定义打分排序,需要使用funcion_score语法,它将query包装在内部,从而实现先召回 -> 再文本相关性评分 -> 最后自定义打分的功能。
官方文档链接
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/query-dsl-function-score-query.html#function-field-value-factor
ES在5.x+版本后发明了一种语法类似javascript/groovy的专用脚本语言painless,我们需要写一个painless脚本,脚本中可以获取文本相关性得分,也可以获取文档的各个字段内容,也可以获取查询请求中传入的临时参数,综合来计算一个新的分数替代默认的文本相关性得分。
ES的自定义排序的推荐两种实现形式:基于脚本的自定义排序和基于native script的自定义排序。
||~版本||使用脚本||
||< Elasticsearch 1.4 ||MVEL 脚本||
||< Elasticsearch 5.0||Groovy 脚本||
||‘>= Elasticsearch 5.0||painless 脚本||
__1. 基于Painless脚本的自定义排序__
先根据排序逻辑定义painless脚本,再用ScriptSortBuilder排序
```
// 自定义评分规则
Map
groupMap.put("华为", 4);
groupMap.put("三星", 3);
groupMap.put("苹果", 2);
groupMap.put("小米", 1);
Map
params.put("groupMap", groupMap);
Script script = new Script(ScriptType.INLINE, SortSetting.SORT_LANG_PAINLESS, SortSetting.SCRIPT_TEXT, params);//脚本文件名称,脚本类型
ScriptSortBuilder sortBuilder = SortBuilders.scriptSort(script, ScriptSortBuilder.ScriptSortType.NUMBER).order(SortOrder.DESC);
```
Painless脚本
```
`def groupScore = params.groupMap[doc['title'].value];groupScore != null?groupScore:0;`
```