【死磕ES】七、基础检索
检索和过滤区别
1)查询器(query):
- 先查询符合搜索条件的文档, 然后计算每个文档对于搜索条件的相关度分数, 再根据评分倒序排序.
2)过滤器(filter):
- 只根据搜索条件过滤出符合的文档, 将这些文档的评分固定为1, 忽略TF/IDF信息, 不计算相关度分数;
- 有cache
- filter 性能更好, 无排序
3)注意:filter和query一起使用时, 会先执行filter.
4)场景:
- 业务关心的、需要根据匹配的相关度进行排序的搜索条件 放在 query 中;
- 业务不关心、不需要根据匹配的相关度进行排序的搜索条件 放在 filter 中.
全文检索
1、分词全文检索【match】,对会先对query进行分词,只要文档里面包含一个query中一个词就会被搜出来
POST bank/_search { "query":{ "match":{ "address":{ "query":"Sedgwick Street aa bb", "operator":"or", "minimum_should_match":2 } } } } //operator:用来控制match查询匹配词条的逻辑条件,默认值是or,如果设置为and,表示查询满足所有条件; //minimum_should_match:当operator参数设置为or时,该参数用来控制应该匹配的分词的最少数量;
2、短语检索【macth_phrase】,也会对query进行分词,但一个文档必须包含query里面所有的词才会被搜出来
POST bank/_search { "query": { "match_phrase": {//表示"query": "Sedgwick Street"中两者同时出现 "address": {//地址属性 "query": "Sedgwick Street", "slop": 3//可选,中间可以有<3个其他的单词 } } } }
3、短语前缀检索【match_phrase_prefix】,除了把查询文本的最后一个分词只做前缀匹配之外,match_phrase_prefix和match_phrase查询基本一样,参数 max_expansions 控制最后一个单词会被重写成多少个前缀,也就是,控制前缀扩展成分词的数量,默认值是50。扩展的前缀数量越多,找到的文档数量就越多;如果前缀扩展的数量太少,可能查找不到相应的文档,遗漏数据。
POST bank/_search { "query": { "match_phrase_prefix": { "address": { "query": "Sedgwick Street", "max_expansions": 10//max_expansions 控制最后一个单词会被重写成多少个前缀,也就是,控制前缀扩展成分词的数量,默认值是50 } } } }
4、多字段匹配检索【multi_match】,在多个字段上执行匹配相同的查询,涉及到匹配评分的问题
POST bank/_search { "query":{ "multi_match":{ "query": "Sedgwick Street", "fields": ["address","firstname"], "operator": "and" } } } //参数operator设置每个字段的子查询的匹配分词的逻辑方式,默认值是or
评分问题
- best_fields
- most_fields
- cross_fields
//我们希望完全匹配的文档占的评分比较高,则需要使用best_fields { "query": { "multi_match": { "query": "我的宝马发动机多少", "type": "best_fields", "fields": [ "tag", "content" ], "tie_breaker": 0.3 } }//匹配"宝马 发动机"的文档评分会比较靠前,如果只匹配宝马的文档评分乘以0.3的系数 } //我们希望越多字段匹配的文档评分越高,就要使用most_fields { "query": { "multi_match": { "query": "我的宝马发动机多少", "type": "most_fields", "fields": [ "tag", "content" ] } } } //我们会希望这个词条的分词词汇是分配到不同字段中的,那么就使用cross_fields { "query": { "multi_match": { "query": "我的宝马发动机多少", "type": "cross_fields", "fields": [ "tag", "content" ] } } }
5、支持与、或、非的字符串检索【query_string】,我们可以通过lucene
的查询语法编写一个查询字符串,然后发送给ES,接收到请求后ES会通过查询解析器去解析查询字符串并生成对应的查询命令。
//单字段的字符串匹配查询 POST bank/_search { "query": { "query_string": { "default_field": "address", "query": "Sedgwick OR Street"//AND、OR必须大写 } } } //多字段的字符串匹配查询 POST bank/_search { "query": { "query_string": { "fields": ["address","firstname"], "query": "Sedgwick AND Street"//不计顺序,AND需要大写 } } }
6、简化的字符串检索【simple_query_string】
POST bank/_search { "query": { "simple_query_string": { "fields": ["address"], "query": "(Sedgwick+Street)|Bay" } } } ①类似Query String,但是会忽略错误的语法,同时只支持部分查询语法 ②不支持AND OR NOT,会当作字符串处理 ③Term之间默认的关系是OR,可以指定Operator支持部分逻辑 ④+替代AND | 替代OR -替代NOT
结构化检索
1、精确匹配检索【term/terms】
- term: {"field": "value"}
- terms: {"field": ["value1", "value2"]} //相当于sql中的in关键字
1)term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
POST bank/_search { "query": { "term": { "firstname": "hattie" } } }
注意:term检索搜索的关键词将不会进行分析处理,会根据输入的搜索词直接进行去lucene倒排索引里面去比对,我们都知道数据在检索时会对要索引的token进行分析处理,其中就包括调用lucene的LowerCaseFilter将token进行小写处理,所以下面使用bankType进行term类型搜索时传入【大写的字母】肯定是查找不到数据的
2)terms //相当于in
POST /forum/article/_search { "query": { "constant_score": { "filter": { "terms": { "tag" : ["java"] } } } } }
2、范围检索【range】
POST /bank/_search { "query": { "range" : { "age" : { "gte" : 10, "lte" : 20, "boost" : 2.0 } } } } // gte: 大于或等于 // gt: 大于 // lte: 小于或等于 // lt: 小于 // boost: 设置查询的提升值,默认为1.0
3、存在与否【exits】
//返回age不为null或者没有值得数据
POST home/_search { "query": { "exists": { "field": "age" } } }
4、前缀检索【prefix】
//firstname中以H开头的数据 POST bank/_search { "query": { "prefix": { "firstname": "h"//注意:要小写,倒排中存的都是小写 } } } POST bank/_search { "query": { "prefix": { "firstname": { "value": "h" } } } }
5、通配符检索【wildcard】,允许使用通配符*和?来进行查询
- *代表0个或多个字符
- ?代表任意1个字符
POST bank/_search { "query": { "wildcard": { "firstname": { "value": "h?ll" } } } }
6、正则检索【regrxp】
POST bank/_search { "query": { "regexp": { "email": "[a-z]*@qq.com" } } }
7、类型检索【type】
8、id检索【ids】
GET /bank/_search { "query": { "ids": { "values": [1,6,20,21] } } }
9、模糊检索【fuzzy】,返回包含与搜索词相似的词的文档
编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。 这些更改可以包括:
- 更改字符(box→fox)
- 删除字符(black→lack)
- 插入字符(sic→sick)
- 转置两个相邻字符(act→cat)
GET /bank/_search { "query": { "fuzzy": { "email": { "value": "fdgt@qq.com", "fuzziness": "AUTO", "max_expansions": 50, "prefix_length": 0, "transpositions": true, "rewrite": "constant_score" } } } }
- value,必填项,希望在field中找到的术语
- fuzziness,选填项,匹配允许的最大编辑距离;参数说明
- max_expansions,选填项,创建的最大变体项,默认为50。应该避免使用较大的值,尤其是当prefix_length参数值为0时,如果过多,会影响查找性能。
- prefix_length,选填项,创建扩展时保留不变的开始字符数。默认为0
- transpositions,选填项,指示编辑是否包括两个相邻字符串的转置(ab→ba)。默认为true
- rewrite,选填项,用于重写查询的方法。高级用法。
复合检索
1、固定得分检索【constant_score】
//constant_score 为filter匹配到的结果给到1.2的分数 GET /_search { "query": { "constant_score" : { "filter" : { "term" : { "user" : "kimchy"} }, "boost" : 1.2//不写默认给到1.0分 } } }
2、bool组合检索,一种能够将多查询组合成单一查询的查询方法,贡献得分来源must&should
它接收以下参数:
1)must,文档 必须 匹配这些条件才能被包含进来,增加得分。相当于sql中的 and
2)should,如果满足这些语句中的任意语句,将增加 _score
,否则,无任何影响。它们主要用于修正每个文档的相关性得分。相当于sql中的or
3)minimun_should_match,match在进行匹配的时候先进行分词,假设分成三个词,然后分别进行should-term匹配,minimun_should_match定义文档中必须包含几个词才参与算分。可以是百分数(向下取整)。比如有5个clause,5*75%=3.75,向下取整为3,也就是至少需要match 3个clause。
4)mast_not,文档 必须不 匹配这些条件才能被包含进来,不贡献得分,起过滤作用。相当于sql中的 not
5)filter,必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档,有缓存。
示例:
POST _search { "query": { "bool" : { "must" : { "term" : { "user" : "kimchy" } }, "filter": { "term" : { "tag" : "tech" } }, "must_not" : { "range" : { "age" : { "gte" : 10, "lte" : 20 } } }, "should" : [ { "term" : { "tag" : "wow" } }, { "term" : { "tag" : "elasticsearch" } } ], "minimum_should_match" : 1, "boost" : 1.0 } } }
示例2:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014-01-01" }}} ] } }
Java代码
BoolQueryBuilder defaultQueryBuilder = QueryBuilders.boolQuery(); defaultQueryBuilder.must(QueryBuilders.matchQuery("title", "how to make millions")) .mustNot(QueryBuilders.matchQuery("tag", "span")) .should(QueryBuilders.matchQuery("tag", "starred")) .should(QueryBuilders.rangeQuery("date").gte("2014-01-01"));
filter用法改造:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }} ], "filter": { "range": { "date": { "gte": "2014-01-01" }} } }
}
注意filter中的条件不参与评分score排序
3、改变评分检索
1)Disjunction max
返回最佳匹配得分,两个字段,第一个文档关键词分散在两字段中,另一个关键词同时出现在同一字段中,实际上需要同时出现的评分更高一些。为了解决这个问题,使用dis_max
GET /_search { "query": { "dis_max" : { "queries" : [ { "term" : { "title" : "Quick pets" }}, { "term" : { "body" : "Quick pets" }} ], "tie_breaker" : 0.7 } } }
说明:1)返回一个会多个查询子句匹配的文档。如果返回的文档与多个查询子句匹配,则dis_max查询会为该文档分配来自所有查询子句的最高相关性得分,再加上其他得分的和与tie_breaker
的积。
换句话说,您可以使用dis_max搜索来降低最佳匹配子句在相关性得分计算中所占的比重。
2)在 dis_max query 中,还有一个参数 tie_breaker
(取值在0~1),在 dis_max query 中,是完全不考虑其他 query 的分数,只是将最佳匹配的字段的评分返回。但是,有的时候,我们又不得不考虑一下其他 query 的分数,此时,可以通过 tie_breaker
来优化 dis_max query。tie_breaker
会将其他 query 的分数,乘以 tie_breaker
,然后和分数最高的 query 进行一个综合计算。
参数:
queries
,必选项。包含一个或多个查询子句。返回的文档,必须匹配其中的一个或多个子句。如果一个文档匹配多个查询子句,Elasticsearch 将会使用最高的相关性得分。tie_breaker
,选填项。介于0,1.0
之间的浮点数。默认为0.0
,用于增加,除最佳匹配子句外的其他匹配子句的占总相关性得分的比重。如果一个文档匹配多个子句,dis_max查询将计算该文档的相关性得分,如下所示:- 从具有最高分数的匹配子句中获取相关性分数。
- 将任何其他匹配子句的分数乘以该
tie_breaker
值。 - 将最高分数加到相乘的分数上。
2)function_score
计算的时候将选举票加入计算评分内,从而改变计算的评分
首先准备两条测试数据
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" } } }
默认情况下,id 为 2 的记录得分较高,因为他的 title 中包含两个 java。
如果我们在查询中,希望能够充分考虑 votes 字段,将 votes 较高的文档优先展示,就可以通过 function_score 来实现。
具体的思路,就是在旧的得分基础上,根据 votes 的数值进行综合运算,重新得出一个新的评分。
具体有几种不同的计算方式:
- weight,对评分设置权重,就是在旧的评分基础上乘以 weight
- random_score
- script_score
- field_value_factor
weight示例:
//在旧评分上乘以权重
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"weight": 10//在之前的评分基础上*
10
}
]
}
}
}
random_score示例:
//根据 uid 字段进行 hash 运算,生成分数,使用 random_score 时可以配置一个种子,如果不配置,默认使用当前时间 GET blog/_search { "query": { "function_score": { "query": { "match": { "title": "java" } }, "functions": [ { "random_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" //现在,最终得分是 (oldScore+votes)*oldScore
。
}
}
}
]
}
}
}
如果不想乘以 oldScore,查询方式如下:
GET blog/_search { "query": { "function_score": { "query": { "match": { "title": "java" } }, "functions": [ { "script_score": { "script": { "lang": "painless", "source": "_score + doc['votes'].value" } } } ], "boost_mode": "replace" } } } 通过 boost_mode 参数,可以设置最终的计算方式。该参数还有其他取值: multiply:分数相乘 sum:分数相加 avg:求平均数 max:最大分 min:最小分 replace:不进行二次计算
field_value_factor示例:
//这个的功能类似于 script_score,但是不用自己写脚本。假设每个文档的最终得分是旧的分数乘以votes。查询方式如下:
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes"//默认的得分就是oldScore*votes
。
}
}
]
}
}
}
还可以利用 es 内置的函数进行一些更复杂的运算:
GET blog/_search { "query": { "function_score": { "query": { "match": { "title": "java" } }, "functions": [ { "field_value_factor": { "field": "votes", "modifier": "sqrt"//此时,最终的得分是(sqrt(votes)) } } ], "boost_mode": "replace" } } }
modifier 中可以设置内置函数,其他的内置函数还有:
另外还有个参数 factor ,影响因子。字段值先乘以影响因子,然后再进行计算。以 sqrt 为例,计算方式为 sqrt(factor*votes):
GET blog/_search { "query": { "function_score": { "query": { "match": { "title": "java" } }, "functions": [ { "field_value_factor": { "field": "votes", "modifier": "sqrt", "factor": 10 } } ], "boost_mode": "replace" } } }
还有一个参数 max_boost,控制计算结果的范围:
GET blog/_search { "query": { "function_score": { "query": { "match": { "title": "java" } }, "functions": [ { "field_value_factor": { "field": "votes" } } ], "boost_mode": "sum", "max_boost": 100 } } }
3)boosting,给negative匹配到的结果减分
GET /_search { "query": { "boosting" : { "positive" : { "term" : { "text" : "apple" } }, "negative" : { "term" : { "text" : "pie tart fruit crumble tree" } }, "negative_boost" : 0.5 } } }
- positive,所有返回文档必须匹配该条件
- negative,获取得分,并将 【得分 * negative_boost】
- negative_boost,将negative匹配的结果降低系数
特定检索
script_score