注:本内容参考的是江南一点雨的这个公众号的,需要数据源可以直接在他的微信公众号后台回复 bookdata.json 下载脚本。

准备工作

  • 下载数据源

  • 创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
PUT books
{
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"publish":{
"type": "text",
"analyzer": "ik_max_word"
},
"type":{
"type": "text",
"analyzer": "ik_max_word"
},
"author":{
"type": "keyword"
},
"info":{
"type": "text",
"analyzer": "ik_max_word"
},
"price":{
"type": "double"
}
}
}
}
  • 在文件目录下执行如下脚本导入命令
1
curl -XPOST "http://localhost:9200/books/_bulk?pretty" -H "content-type:application/json" --data-binary @bookdata.json

查询分为两个过程:

  1. 当向索引中保存文档时,默认情况下,es 会保存两份内容,一份是 _source 中的数据,另一份则是通过分词、排序等一系列过程生成的倒排索引文件,倒排索引中保存了词项和文档之间的对应关系。
  2. 搜索时,当 es 接收到用户的搜索请求之后,就会去倒排索引中查询,通过的倒排索引中维护的倒排记录表找到关键词对应的文档集合,然后对文档进行评分、排序、高亮等处理,处理完成后返回文档。

基础查询

简单查询(会返回10条记录):

1
2
3
4
5
6
7
GET books/_search
{
"query": {
"match_all": {}
}
}
# 或者直接GET books/_search

词项查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET books/_search
{
"query": {
"term": {
"name": "数学"
}
}
}
# 或者使用terms设置多个查询条件
{
"query": {
"terms": {
"name": ["计算机","数学"]
}
}
}

分页查询

默认返回前 10 条数据,es 中也可以像关系型数据库一样,给一个分页参数:

1
2
3
4
5
6
7
8
9
10
GET books/_search
{
"query": {
"term": {
"name": "数学"
}
},
"size": 10,
"from": 10
}

过滤返回字段

​ 如果返回的字段比较多,又不需要这么多字段,此时可以指定返回的字段,如下文中这么设置,则结果就只返回name和author了:

1
2
3
4
5
6
7
8
9
10
11
GET books/_search
{
"query": {
"term": {
"name": "数学"
}
},
"size": 10,
"from": 10,
"_source": ["name","author"]
}

最小评分

​ 有的文档得分特别低,说明这个文档和我们查询的关键字相关度很低。我们可以设置一个最低分,只有得分超过最低分的文档才会被返回,如下查询条件中得分低于 1.75 的文档将直接被舍弃。

1
2
3
4
5
6
7
8
9
10
GET books/_search
{
"query": {
"term": {
"name": "数学"
}
},
"min_score":1.75,
"_source": ["name","author"]
}

高亮

查询关键字高亮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
GET books/_search
{
"query": {"term": {"name": "数学"}},
"highlight": {
"fields": {
"name": {}
}
}
}

# 也可以设置高亮类型
{
"query":{
"term":{
"name":"数学"
}
},
"highlight":{
"fields":{
"name":{
"pre_tags":[
"<strong>"
],
"post_tags":[
"</strong>"
]
}
}
}
}
# 当然,name 字段中搜索的,但是我们希望 info 字段中,相关的关键字也能高亮
{
"query": {
"match": {
"name": "数学"
}
},
"highlight": {
"require_field_match": "false",
"fields": {
"name": {
"pre_tags": ["<strong>"],
"post_tags": ["</strong>"]
},
"info": {
"pre_tags": ["<strong>"],
"post_tags": ["</strong>"]
}
}
}
}

排序

排序很简单,默认是按照查询文档的相关度来排序的,即(_score 字段):

1
2
3
4
5
6
7
8
9
10
GET books/_search
{
"query": {
"term": {
"name": {
"value": "java"
}
}
}
}

等价于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET books/_search
{
"query": {
"term": {
"name": {
"value": "java"
}
}
},
"sort": [
{
"_score": {
"order": "desc"
}
}
]
}

match_all 查询只是返回所有文档,不评分,默认按照添加顺序返回,可以通过 _doc 字段对其进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET books/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_doc": {
"order": "desc"
}
}
],
"size": 20
}

es同时也支持多字段排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET books/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "asc"
}
},
{
"_doc": {
"order": "desc"
}
}
],
"size": 20
}

全文查询

match

​ match query 会对查询语句进行分词,分词后,如果查询语句中的任何一个词项被匹配,则文档就会被索引到。

1
2
3
4
5
6
7
8
GET books/_search
{
"query": {
"match": {
"name": "美术计算机"
}
}
}

​ 这个查询首先会对 美术计算机 进行分词,分词之后,再去查询,只要文档中包含一个分词结果,就回返回文档。换句话说,默认词项之间是 OR 的关系,如果想要修改,也可以改为 AND。

1
2
3
4
5
6
7
8
9
10
11
GET books/_search
{
"query": {
"match": {
"name": {
"query": "美术计算机",
"operator": "and"
}
}
}
}

此时就回要求文档中必须同时包含 美术计算机 两个词。

match_phrase

match_phrase query 也会对查询的关键字进行分词,但是它分词后有两个特点:

  • 分词后的词项顺序必须和文档中词项的顺序一致
  • 所有的词都必须出现在文档中

示例如下:

1
2
3
4
5
6
7
8
9
10
11
GET books/_search
{
"query": {
"match_phrase": {
"name": {
"query": "高等教育计算机",
"slop": 100
}
}
}
}

query 是查询的关键字,会被分词器进行分解,分解之后去倒排索引中进行匹配。

slop是指关键字之间的最小距离,但是注意不是关键之间间隔的字数。文档中的字段被分词器解析之后,解析出来的词项都包含一个 position 字段表示词项的位置,查询短语分词之后的 position 之间的间隔要满足 slop 的要求。

match_phrase_prefix

​ 这个类似于 match_phrase,只不过这里多了一个通配符,match_phrase_prefix 支持最后一个词项的前缀匹配,但是由于这种匹配方式效率较低,因此大家作为了解即可。

1
2
3
4
5
6
7
8
GET books/_search
{
"query": {
"match_phrase_prefix": {
"name": "计"
}
}
}

​ 这个查询过程,会自动进行单词匹配,会自动查找以开始的单词,默认是 50 个,可以自己控制:

1
2
3
4
5
6
7
8
9
10
11
GET books/_search
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "计",
"max_expansions": 3
}
}
}
}

​ match_phrase_prefix 是针对分片级别的查询,假设 max_expansions 为 1,可能返回多个文档,但是只有一个词,这是我们预期的结果。有的时候实际返回结果和我们预期结果并不一致,原因在于这个查询是分片级别的,不同的分片确实只返回了一个词,但是结果可能来自不同的分片,所以最终会看到多个词。

multi_match

match 查询的升级版,可以指定多个查询域:

1
2
3
4
5
6
7
8
9
GET books/_search
{
"query": {
"multi_match": {
"query": "java",
"fields": ["name","info"]
}
}
}

这种查询方式还可以指定字段的权重:

1
2
3
4
5
6
7
8
9
GET books/_search
{
"query": {
"multi_match": {
"query": "阳光",
"fields": ["name^4","info"]
}
}
}

这个表示关键字出现在 name 中的权重是出现在 info 中权重的 4 倍。

query_string

query_string 是一种紧密结合 Lucene 的查询方式,在一个查询语句中可以用到 Lucene 的一些查询语法:

1
2
3
4
5
6
7
8
9
GET books/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "(十一五) AND (计算机)"
}
}
}

simple_query_string

这个是 query_string 的升级,可以直接使用 +、|、- 代替 AND、OR、NOT 等,查询结果和 query_string相同。

1
2
3
4
5
6
7
8
9
GET books/_search
{
"query": {
"simple_query_string": {
"fields": ["name"],
"query": "(十一五) + (计算机)"
}
}
}

其他查询

range

​ 范围查询,可以按照日期范围、数字范围等查询,range query 中的参数主要有四个:gt (大于)、lt (小于)、gte (大于等于)、lte (小于等于)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET books/_search
{
"query": {
"range": {
"price": {
"gte": 10,
"lt": 20
}
}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}

exists

​ exists query 会返回指定字段中至少有一个非空值的文档:

1
2
3
4
5
6
7
8
GET books/_search
{
"query": {
"exists": {
"field": "计算机"
}
}
}

注意,空字符串也是有值。null 是空值。

Prefix

前缀查询,效率略低,除非必要,一般不太建议使用。

给定关键词的前缀去查询:

1
2
3
4
5
6
7
8
9
10
GET books/_search
{
"query": {
"prefix": {
"name": {
"value": "计算机"
}
}
}
}

wildcard

wildcard query 即通配符查询。支持单字符和多字符通配符:

  • ?表示一个任意字符。
  • * 表示零个或者多个字符。

查询所有姓张的作者的书:

1
2
3
4
5
6
7
8
9
10
GET books/_search
{
"query": {
"wildcard": {
"author": {
"value": "张*"
}
}
}
}

查询所有姓张并且名字只有两个字的作者的书:

1
2
3
4
5
6
7
8
9
10
GET books/_search
{
"query": {
"wildcard": {
"author": {
"value": "张?"
}
}
}
}

regexp

支持正则表达式查询。

查询所有姓张并且名字只有两个字的作者的书:

1
2
3
4
5
6
7
8
GET books/_search
{
"query": {
"regexp": {
"author": "张."
}
}
}

fuzzy

​ 在实际搜索中,有时我们可能会打错字,从而导致搜索不到,在 match query 中,可以通过 fuzziness 属性实现模糊查询。

​ fuzzy query 返回与搜索关键字相似的文档。怎么样就算相似?以LevenShtein 编辑距离为准。编辑距离是指将一个字符变为另一个字符所需要更改字符的次数,更改主要包括四种:

  • 更改字符(javb–>java)
  • 删除字符(javva–>java)
  • 插入字符(jaa–>java)
  • 转置字符(ajva–>java)

​ 为了找到相似的词,模糊查询会在指定的编辑距离中创建搜索关键词的所有可能变化或者扩展的集合,然后进行搜索匹配。

1
2
3
4
5
6
7
8
GET books/_search
{
"query": {
"fuzzy": {
"name": "javva"
}
}
}

ids query

根据指定的 id 查询。

1
2
3
4
5
6
7
8
GET books/_search
{
"query": {
"ids":{
"values": [1,2,3]
}
}
}

参考:https://www.bilibili.com/video/BV1ft4y1e7tq