搜索分为两个过程:
- 当向索引中保存文档时,默认情况下,es 会保存两份内容,一份是 _source 中的数据,另一份则是通过分词、排序等一系列过程生成的倒排索引文件,倒排索引中保存了词项和文档之间的对应关系。
- 搜索时,当 es 接收到用户的搜索请求之后,就会去倒排索引中查询,通过的倒排索引中维护的倒排记录表找到关键词对应的文档集合,然后对文档进行评分、排序、高亮等处理,处理完成后返回文档。
简单搜索
查询文档
查询全部,系统默认返回 10 条
GET books/_search
{
"query": {
"match_all": {}
}
}
hits 中就是查询结果,total 是符合查询条件的文档数。
简单搜索可以简写为:
GET books/_search
词项查询 (term 查询)
term查询会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合keyword 、numeric、date。
根据词去查询,查询指定字段中包含给定单词的文档,term 查询不被解析,只有搜索的词和文档中的词精确匹配,才会返回文档。应用场景如:人名、地名等等。
查询 name 字段中包含 十一五 的文档。
GET books/_search
{
"query": {
"term": {
"name": "十一五"
}
}
}
terms 查询
词项查询,但是可以给多个关键词。
GET books/_search
{
"query": {
"terms": {
"name": ["程序","设计","java"]
}
}
}
分页
默认返回前 10 条数据,es 中也可以像关系型数据库一样,给一个分页参数:
GET books/_search
{
"query": {
"term": {
"name": "十一五"
}
},
"size": 10,
"from": 10
}
过滤返回字段
如果返回的字段比较多,又不需要这么多字段,此时可以指定返回的字段:
GET books/_search
{
"query": {
"term": {
"name": "十一五"
}
},
"size": 10,
"from": 10,
"_source": ["name","author"]
}
此时,返回的字段就只有 name 和 author 了。
最小评分
有的文档得分特别低,说明这个文档和我们查询的关键字相关度很低。我们可以设置一个最低分,只有得分超过最低分的文档才会被返回。
GET books/_search
{
"query": {
"term": {
"name": "十一五"
}
},
"min_score":1.75,
"_source": ["name","author"]
}
得分低于 1.75 的文档将直接被舍弃。
高亮
查询关键字高亮:
GET books/_search
{
"query": {
"term": {
"name": "十一五"
}
},
"min_score":1.75,
"_source": ["name","author"],
"highlight": {
"fields": {
"name": {}
}
}
}
返回版本号
GET /lib3/user/_search
{
"version":true,
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
全文查询
match 查询
match query 会对查询语句进行分词,分词后,如果查询语句中的任何一个词项被匹配,则文档就会被索引到。
GET books/_search
{
"query": {
"match": {
"name": "美术计算机"
}
}
}
这个查询首先会对 美术计算机
进行分词,分词之后,再去查询,只要文档中包含一个分词结果,就回返回文档。换句话说,默认词项之间是 OR 的关系,如果想要修改,也可以改为 AND。
GET books/_search
{
"query": {
"match": {
"name": {
"query": "美术计算机",
"operator": "and"
}
}
}
}
此时就回要求文档中必须同时包含 美术
和 计算机
两个词。
短语匹配查询 match_phrase query
match_phrase query 也会对查询的关键字进行分词,但是它分词后有两个特点:
- 分词后的词项顺序必须和文档中词项的顺序一致
- 所有的词都必须出现在文档中
这意味着必须匹配短语中的所有分词,并且保证各个分词的相对位置不变, 示例如下:
GET books/_search
{
"query": {
"match_phrase": {
"name": {
"query": "十一五计算机",
"slop": 7
}
}
}
}
query 是查询的关键字,会被分词器进行分解,分解之后去倒排索引中进行匹配。
slop 是指关键字之间的最小距离,但是注意不是关键之间间隔的字数。文档中的字段被分词器解析之后,解析出来的词项都包含一个 position 字段表示词项的位置,查询短语分词之后 的 position 之间的间隔要满足 slop 的要求。
前缀匹配查询 match_phrase_prefix
这个类似于 match_phrase query,只不过这里多了一个通配符,match_phrase_prefix 支持最后一个词项的前缀匹配,但是由于这种匹配方式效率较低,因此大家作为了解即可。
GET books/_search
{
"query": {
"match_phrase_prefix": {
"name": "计"
}
}
}
这个查询过程,会自动进行单词匹配,会自动查找以计开始的单词,默认是 50 个,可以自己控制:
GET books/_search
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "计",
"max_expansions": 3
}
}
}
}
match_phrase_prefix 是针对分片级别的查询,假设 max_expansions 为 1,可能返回多个文档,但是只有一个词,这是我们预期的结果。有的时候实际返回结果和我们预期结果并不一致,原因在于这个查询是分片级别的,不同的分片确实只返回了一个词,但是结果可能来自不同的分片,所以最终会看到多个词。
指定多个字段查询 multi_match
match 查询的升级版,可以指定多个查询域:
GET books/_search
{
"query": {
"multi_match": {
"query": "java",
"fields": ["name","info"]
}
}
}
这种查询方式还可以指定字段的权重:
GET books/_search
{
"query": {
"multi_match": {
"query": "阳光",
"fields": ["name^4","info"]
}
}
}
这个表示关键字出现在 name 中的权重是出现在 info 中权重的 4 倍。
query_string 查询
query_string 是一种紧密结合 Lucene 的查询方式,在一个查询语句中可以用到 Lucene 的一些查询语法:
GET books/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "(十一五) AND (计算机)"
}
}
}
高级 query_string 查询:simple_query_string
这个是 query_string 的升级,可以直接使用 +、|、- 代替 AND、OR、NOT 等。
GET books/_search
{
"query": {
"simple_query_string": {
"fields": ["name"],
"query": "(十一五) + (计算机)"
}
}
}
查询结果和 query_string 一样。
范围查询 range
范围查询,可以按照日期范围、数字范围等查询
include_lower:是否包含范围的左边界,默认是true
include_upper:是否包含范围的右边界,默认是true
range query 中的参数主要有四个:
- gt
- lt
- gte
- lte
GET books/_search
{
"query": {
"range": {
"price": {
"gte": 10,
"lt": 20
}
}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
还有一种
include_lower:是否包含范围的左边界,默认是true
include_upper:是否包含范围的右边界,默认是true
GET /lib3/user/_search
{
"query": {
"range": {
"birthday": {
"from": "1990-10-10",
"to": "2018-05-01"
}
}
}
}
GET /lib3/user/_search
{
"query": {
"range": {
"age": {
"from": 20,
"to": 25,
"include_lower": true,
"include_upper": false
}
}
}
}
非空查询 exists
exists query 会返回指定字段中至少有一个非空值的文档:
GET books/_search
{
"query": {
"exists": {
"field": "javaboy"
}
}
}
注意,空字符串也是有值。null 是空值。
前缀查询 prefix
前缀查询,效率略低,除非必要,一般不太建议使用。
给定关键词的前缀去查询:
GET books/_search
{
"query": {
"prefix": {
"name": {
"value": "大学"
}
}
}
}
通配符查询 wildcard
通配符查询。支持单字符和多字符通配符:
- ?表示一个任意字符。
- * 表示零个或者多个字符。
查询所有姓张的作者的书:
GET books/_search
{
"query": {
"wildcard": {
"author": {
"value": "张*"
}
}
}
}
查询所有姓张并且名字只有两个字的作者的书:
GET books/_search
{
"query": {
"wildcard": {
"author": {
"value": "张?"
}
}
}
}
正则表达式查询
支持正则表达式查询。
查询所有姓张并且名字只有两个字的作者的书:
GET books/_search
{
"query": {
"regexp": {
"author": "张."
}
}
}
模糊查询 fuzzy
在实际搜索中,有时我们可能会打错字,从而导致搜索不到,在 match query 中,可以通过 fuzziness 属性实现模糊查询。
fuzzy query 返回与搜索关键字相似的文档。怎么样就算相似?以LevenShtein 编辑距离为准。编辑距离是指将一个字符变为另一个字符所需要更改字符的次数,更改主要包括四种:
- 更改字符(javb--〉java)
- 删除字符(javva--〉java)
- 插入字符(jaa--〉java)
- 转置字符(ajva--〉java)
为了找到相似的词,模糊查询会在指定的编辑距离中创建搜索关键词的所有可能变化或者扩展的集合,然后进行搜索匹配。
GET books/_search
{
"query": {
"fuzzy": {
"name": "javba"
}
}
}
ids 查询
根据指定的 id 查询。
GET books/_search
{
"query": {
"ids":{
"values": [1,2,3]
}
}
}
复合查询
constant_score 查询
当我们不关心检索词项的频率(TF)对搜索结果排序的影响时,可以使用 constant_score 将查询语句或者过滤语句包裹起来。
GET books/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"name": "java"
}
},
"boost": 1.5
}
}
}
bool 查询
bool query 可以将任意多个简单查询组装在一起,有四个关键字可供选择,四个关键字所描述的条件可以有一个或者多个。
- must:文档必须匹配 must 选项下的查询条件。
- should:文档可以匹配 should 下的查询条件,也可以不匹配。
- must_not:文档必须不满足 must_not 选项下的查询条件。
- filter:类似于 must,但是 filter 不评分,只是过滤数据。
例如查询 name 属性中必须包含 java,同时书价不在 [0,35] 区间内,info 属性可以包含 程序设计 也可以不包含程序设计:
GET books/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": {
"value": "java"
}
}
}
],
"must_not": [
{
"range": {
"price": {
"gte": 0,
"lte": 35
}
}
}
],
"should": [
{
"match": {
"info": "程序设计"
}
}
]
}
}
}
这里还涉及到一个关键字,minmum_should_match
参数。
minmum_should_match
参数在 es 官网上称作最小匹配度。在之前学习的 multi_match 或者这里的 should
查询中,都可以设置 minmum_should_match
参数。
假设我们要做一次查询,查询 name 中包含 语言程序设计 关键字的文档:
GET books/_search
{
"query": {
"match": {
"name": "语言程序设计"
}
}
}
在这个查询过程中,首先会进行分词,分词结果如下:
分词后的 term 会构造成一个 should 的 bool query,每一个 term 都会变成一个 term query 的子句。换句话说,上面的查询和下面的查询等价:
GET books/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"name": {
"value": "语言"
}
}
},
{
"term": {
"name": {
"value": "程序设计"
}
}
},
{
"term": {
"name": {
"value": "程序"
}
}
},
{
"term": {
"name": {
"value": "设计"
}
}
}
]
}
}
}
在这两个查询语句中,都是文档只需要包含词项中的任意一项即可,文档就回被返回,在 match 查询中,可以通过 operator 参数设置文档必须匹配所有词项。
如果想匹配一部分词项,就涉及到一个参数,就是 minmum_should_match
,即最小匹配度。即至少匹配多少个词。
GET books/_search
{
"query": {
"match": {
"name": {
"query": "语言程序设计",
"operator": "and"
}
}
}
}
GET books/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"name": {
"value": "语言"
}
}
},
{
"term": {
"name": {
"value": "程序设计"
}
}
},
{
"term": {
"name": {
"value": "程序"
}
}
},
{
"term": {
"name": {
"value": "设计"
}
}
}
],
"minimum_should_match": "50%"
}
},
"from": 0,
"size": 70
}
50% 表示词项个数的 50%。
如下两个查询等价(参数 4 是因为查询关键字分词后有 4 项):
GET books/_search
{
"query": {
"match": {
"name": {
"query": "语言程序设计",
"minimum_should_match": 4
}
}
}
}
GET books/_search
{
"query": {
"match": {
"name": {
"query": "语言程序设计",
"operator": "and"
}
}
}
}
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解决方案"
}
}
]
}
}
}
搜索结果如下:
肉眼观察,感觉第二个和查询关键字相似度更高,但是实际查询结果并非这样。
要理解这个原因,我们需要来看下 should query 中的评分策略:
- 首先会执行 should 中的两个查询
- 对两个查询结果的评分求和
- 对求和结果乘以匹配语句总数
- 在对第三步的结果除以所有语句总数
反映到具体的查询中:
前者
- title 中 包含 java,假设评分是 1.1
- content 中包含解决方案,假设评分是 1.2
- 有得分的 query 数量,这里是 2
- 总的 query 数量也是 2
最终结果:(1.1+1.2)*2/2=2.3
后者
- title 中 不包含查询关键字,没有得分
- content 中包含解决方案和 java,假设评分是 2
- 有得分的 query 数量,这里是 1
- 总的 query 数量也是 2
最终结果:2*1/2=1
在这种查询中,title 和 content 相当于是相互竞争的关系,所以我们需要找到一个最佳匹配字段。
为了解决这一问题,就需要用到 dis_max query(disjunction max query,分离最大化查询):匹配的文档依然返回,但是只将最佳匹配的评分作为查询的评分。
GET blog/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"title": "java解决方案"
}
},
{
"match": {
"content": "java解决方案"
}
}
]
}
}
}
查询结果如下:
在 dis_max query 中,还有一个参数 tie_breaker
(取值在0~1),在 dis_max query 中,是完全不考虑其他 query 的分数,只是将最佳匹配的字段的评分返回。但是,有的时候,我们又不得不考虑一下其他 query 的分数,此时,可以通过 tie_breaker
来优化 dis_max query。tie_breaker
会将其他 query 的分数,乘以 tie_breaker
,然后和分数最高的 query 进行一个综合计算。