本学习笔记基于ElasticSearch 7.10版本,旧版本已经废弃的查询功能暂时不做笔记,以后有涉及到再做补充。
参考官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/compound-queries.html
1、constant_score
当我们使用全文查询或词项查询时,ElasticSearch 默认会根据词频(TF)对查询结果进行评分,再根据评分排序后返回查询结果。
如果我们不关心检索词频(TF)对搜索结果排序的影响时,可以使用 constant_score
将查询语句或者过滤语句包裹起来。
GET books/_search
{
"query": {
"term": {
"name": "java"
}
}
}
GET books/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"name": "java"
}
},
"boost": 1.5
}
}
}
2、bool
bool
底层使用的是 Lucene 的 BooleanQuery
,可以将任意多个简单查询组装在一起,有四个关键字可供选择,四个关键字所描述的条件可以有一个或者多个。
- must :文档必须匹配 must 选项下的查询条件。
- should :文档可以匹配 should 下的查询条件,也可以不匹配。
- must_not :文档必须不满足 must_not 选项下的查询条件。
- filter :类似于 must,但是 filter 不评分,只是过滤数据。
例如查询 name 字段中必须包含 “计算”,同时书价不在 [0,30] 区间内,info 字段可以包含 “程序设计” 也可以不包含:
GET books/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": "计算"
}
}
],
"must_not": [
{
"range": {
"price": {
"gte": 1,
"lte": 30
}
}
}
],
"should": [
{
"match": {
"info": "程序设计"
}
}
]
}
},
"sort": [
{
"price": {
"order": "asc"
}
}
],
"from": 0,
"size": 40
}
minmum_should_match
bool
查询还涉及到一个关键字 minmum_should_match
参数,在 ElasticSearch 官网上称作 “最小匹配度” 。在之前学习的 match
或者这里的 should
查询中,都可以设置 minmum_should_match
参数。
假设我们查询 name 中包含 “程序设计” 关键字的文档:
GET books/_search
{
"query": {
"match": {
"name": "程序设计"
}
}
}
首先对关键字进行分词,分词结果如下:
然后使用 should
将分词后的每一个词项查询 term
子句组合构成一个 bool
查询。换句话说,上面的查询和下面的查询等价:
GET books/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"name": "程序设计"
}
},
{
"term": {
"name": "程序"
}
},
{
"term": {
"name": "设计"
}
}
]
}
}
}
在这两个查询语句中,都是文档只需要包含词项中的任意一项即可,文档就回被返回,在 match
查询中,可以通过 operator
参数设置文档必须匹配所有词项。
但如果只想匹配一部分词项,就要使用 minmum_should_match
,指定至少匹配的词项,可以指定个数或者百分比,以下三个查询等价:
GET books/_search
{
"query": {
"match": {
"name": {
"query": "程序设计",
"operator": "and"
}
}
}
}
# 指定个数
GET books/_search
{
"query": {
"match": {
"name": {
"query": "程序设计",
"minimum_should_match": 3
}
}
}
}
# 指定百分比
GET books/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"name": "程序设计"
}
},
{
"term": {
"name": "程序"
}
},
{
"term": {
"name": "设计"
}
}
],
"minimum_should_match": "100%"
}
}
}
3、dis_max
之前的books 索引中的数据不好演示 dis_max
查询,我们使用一些新的数据:
PUT blog
{
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "ik_max_word"
},
"content":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
POST blog/_doc
{
"title":"如何通过Java代码调用ElasticSearch",
"content":"大方哥力荐,这是一篇很好的解决方案"
}
POST blog/_doc
{
"title":"初识 MongoDB",
"content":"简单介绍一下 MongoDB,以及如何通过 Java 调用 MongoDB,MongoDB 是一个不错 NoSQL 解决方案"
}
现在假设搜索 “Java解决方案” 关键字,但是不确定关键字是在 title 还是在 content,所以两者都搜索:
GET blog/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "Java解决方案"
}
},
{
"match": {
"content": "Java解决方案"
}
}
]
}
}
}
按照我们理解,第二篇文档中的content同时包含了 “Java” 和 “解决方案” ,应该评分更高。但是实际搜索并非这样,第一篇文档的评分反而比较高:
要理解为什么会这样,我们需要看一下 should
查询的评分策略:
1、 首先执行should
中的所有查询;
2、 对所有查询结果的评分求和;
3、 对求和结果*有查询结果的子句数量;
4、 再将上一步结果/所有查询子句数量;
根据评分策略,再来看看具体的查询:
第一篇
1、 title中包含Java,假设评分是1.1;
2、 content中包含解决方案,假设评分是1.2;
3、 有查询结果的子句数量为2;
4、 所有查询子句数量也是2;
最终结果:(1.1 + 1.2)* 2 / 2 = 2.3
第一篇
1、 title中不包含查询关键字,没有得分;
2、 content中包含Java和解决方案,假设评分时2;
3、 有查询结果的子句数量为1;
4、 所有查询子句数量也是2;
最终结果:2 * 1 / 2 = 1
在should
查询中,title 和 content 相当于是相互竞争的关系。
为了解决这一问题,就需要用到 dis_max
(disjunction max query,分离最大化查询):匹配的文档依然返回,但是只将最佳匹配的子句评分作为查询的评分。
GET blog/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"title": "Java解决方案"
}
},
{
"match": {
"content": "Java解决方案"
}
}
]
}
}
}
在dis_max
查询中,只考虑最佳匹配子句的评分,完全不考虑其他子句的评分。但是,有的时候,我们又不得不考虑一下其他 query 的分数,此时,可以通过 tie_breaker
参数来优化。
tie_breaker
参数取值范围:0 ~ 1,默认取值为 0,会将其他查询子句的评分,乘以 tie_breaker
参数,然后再和最佳匹配的子句进行综合计算。
GET blog/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"title": "Java解决方案"
}
},
{
"match": {
"content": "Java解决方案"
}
}
],
"tie_breaker": 0.5
}
}
}
4、function_score
场景:例如想要搜索附近的肯德基,搜索的关键字是肯德基,我希望能够将评分较高的肯德基餐厅优先展示出来。但是默认的评分策略是没有办法考虑到餐厅评分的,他只是考虑相关性,这个时候可以通过 function_score query
来实现。
准备测试数据:
PUT blog
{
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "ik_max_word"
},
"votes":{
"type": "integer"
}
}
}
}
PUT blog/_doc/1
{
"title":"Java集合详解",
"votes":100
}
PUT blog/_doc/2
{
"title":"Java多线程详解,Java锁详解",
"votes":10
}
查询标题中包含 java 关键字的文档:
GET blog/_search
{
"query": {
"match": {
"title": "java"
}
}
}
查询结果如下:
因为第二篇文档的 title 中有两个 java,所有得分会比较高,如果希望能够考虑 votes 字段,将 votes 高的文档优先展示,就可以通过 function_score
来实现。
具体的思路,就是在旧的得分基础上,根据 votes 的数值进行综合运算,重新得出一个新的评分。具体的计算方式有以下几种:
- weight
- random_score
- script_score
- field_value_factor
- decay functions (本文不做介绍)
4.1、weight
weight
可以对评分设置权重,在旧的评分基础上乘以 weight
,他其实无法解决我们上面所说的问题。具体用法如下:
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"weight": 10
}
]
}
}
}
4.2、random_score
random_score
会根据 uid 字段进行 hash 运算,生成随机分数,使用 random_score
时可以配置一个种子,如果不配置,默认使用当前时间。
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"random_score": {
}
}
]
}
}
}
random_score
也不能解决我们的问题。
4.3、script_score
script_score
是自定义评分脚本。
假设每个文档的最终得分是旧的分数加上 votes,查询方式如下:
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"script_score": {
"script": {
"lang": "painless",
"source": "_score + doc['votes'].value"
}
}
}
]
}
}
}
查询结果得分如下:
注意: ElasticSearch 并不是简单的将旧得分和 votes 相加,底层的计算公式是:(旧得分 + votes) * 旧得分
。第一篇文档的旧得分为 0.21799318,代入计算 :(0.21799318 + 100) * 0.21799318 = 21.84684
。
如果不想乘以旧得分,查询方式如下:
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"script_score": {
"script": "_score + doc['votes'].value"
}
}
],
"boost_mode": "replace"
}
}
}
通过boost_mode
参数,可以设置最终的计算方式。该参数还有其他取值:
- multiply:查询得分和 functions 得分相乘 (默认)
- replace:只使用 functions 得分,忽略查询得分
- sum:查询得分和 functions 得分相加
- avg:查询得分和 functions 得分的平均值
- max:查询得分和 functions 得分的最大值
- min:查询得分和 functions 得分的最小值
4.4、field_value_factor
field_value_factor
类似于 script_score
,但是不用自己写脚本,而是使用影响因子和内置函数进行计算。具体有以下三个参数:
1、 field
:需要进行计算的文档字段;
2、 factor
:计算时与字段值相乘的影响因子,默认为1;
3、 modifier
:可选择ElasticSearch内置函数,默认不使用;
下面演示一个稍复杂查询,将 votes 字段值乘以影响因子 1.6,结果再取平方根,最后忽略 query
查询的分数 sqrt(1.2 * doc['votes'].value)
:
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes",
"factor": 1.6,
"modifier": "sqrt"
}
}
],
"boost_mode": "replace"
}
}
}
modifier
其他的内置函数还有:
- none:不进行任何计算 (默认)
- log:字段值取常用对数。如果字段值为0到1之间,将导致错误,可以考虑使用下面的 log1p 函数
- log1p:字段值加1后,取常用对数
- log2p:字段值加2后,取常用对数
- ln:字段值取自然对数。如果字段值为0到1之间,将导致错误,可以考虑使用下面的 ln1p 函数
- ln1p:字段值加1后,取自然对数
- ln2p:字段值加2后,取自然对数
- square:字段值的平方
- sqrt:字段值求平方根
- reciprocal:字段值取倒数
4.5、其他参数:
score_mode
参数表示 functions
模块中不同计算方法之间的得分计算方式,有以下几种:
- multiply:得分相乘 (默认)
- sum:得分相加
- avg:得分取平均值
- first:取第一个有效的方法得分
- max:最大的得分
- min:最小的得分
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes",
"factor": 1.6,
"modifier": "sqrt"
}
},
{
"script_score": {
"script": "_score + doc['votes'].value"
}
}
],
"boost_mode": "replace"
}
}
}
max_boost
参数表示 functions
模块中得分上限:
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes",
"factor": 1.6,
"modifier": "sqrt"
}
}
],
"boost_mode": "replace",
"max_boost": 10
}
}
}
5、boosting
boosting
中包含三部分:
- positive:得分不变
- nagetive:降低得分
- nagetive_boost:降低得分的权重,取值范围 [0 ~1]
# 正常查询
GET books/_search
{
"query": {
"match": {
"name": "java"
}
}
}
# 使用 nagetive 和 nagetive_boost 降低字段值中包含 “2008” 的文档得分
GET books/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"name": "java"
}
},
"negative": {
"match": {
"name": "2008"
}
},
"negative_boost": 0.5
}
}
}
版权声明:
本文仅记录ElasticSearch学习心得,如有侵权请联系删除。
更多内容请访问原创作者:江南一点雨
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: