ElasticSearch 入门教程 - 基础篇之二

大纲

版本说明

软件版本描述
ElasticSearch7.4.2ElasticSearch 的版本
Curl7.29.0Curl 的版本

ElasticSearch 导入数据

学习 ES 的时候,往往需要大量的测试数据,建议导入 ES 官方 GitHub 仓库中的 JSON 数据文件,这样方便后续学习 ES 的复杂查询。首先将 ES 官方提供的 JSON 数据保存到 accounts.json 文件里,或者直接从本站下载 数据文件,然后执行以下命令批量导入数据。

1
curl -X POST -H 'Content-Type:application/json' http://127.0.0.1:9200/bank/account/_bulk --data-binary @accounts.json

ElasticSearch 检索操作

Search API 的使用

ES 支持下述两种基本的检索方式,分别是 URI + 检索参数URI + 请求体

第一种使用方式

使用 REST Request URI 发送检索参数(URI + 检索参数)。

检索示例描述
GET bank/_search检索 bank 索引下的所有信息,包括 type 和 docs
GET bank/_search?q=*&sort=account_number:asc请求参数方式检索
1
2
# 查询 bank 索引下的所有数据,并按照 account_number 字段升序排序
curl -X GET -H 'Content-Type:application/json' http://127.0.0.1:9200/bank/_search?q=*&sort=account_number:asc

第二种使用方式

使用 REST Request Body 来发送检索参数(URI + 请求体)。

1
2
# 查询 bank 索引下的所有数据,并按照 account_number 字段升序排序
curl -X POST -H 'Content-Type:application/json' http://127.0.0.1:9200/bank/_search -d '{"query":{"match_all":{}},"sort":[{"account_number":{"order":"asc"}}]}'

上述的 Curl 命令,POST 了一个 JSON 风格的查询请求体到 Search API,请求体的内容就是 Query DSL。值得一提的是,一旦搜索的结果被返回,Elasticsearch 就完成了这次请求,并且不会维护任何服务端的资源或者结果的 Cursor(游标)。

返回结果的说明

返回结果的字段描述
took 执行搜索的耗时(毫秒)
time_out 搜索是否超时
_shards 多少个分片被搜索了,以及统计了成功 / 失败的搜索分片
hits 搜索结果
hits.total 搜索结果统计
hits.hits 实际的搜索结果数组(默认只搜索前 10 条文档)
hits.sort 结果的排序 Key(键),没有则按 score 排序
hits.score 相关性得分(全文检索用)
max_score 相关性的最高得分(全文检索用)

Query DSL 的使用

Elasticsearch 提供了一个可以执行查询的 Json 风格的 DSL(Domain Specific Language - 领域特定语言),这个被称为 Query DSL。该查询语言非常全面,虽然刚开始使用的时候感觉有点复杂,但熟悉了之后会发现很实用。

语法格式

  • 一个查询语句的典型结构
1
2
3
4
5
6
7
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,
...
}
}
  • 如果是针对某个字段查询,那么它的结构如下
1
2
3
4
5
6
7
8
9
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,
...
}
}
}

简单查询

  • 查询前 5 条记录,并按照 account_number 字段倒序排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"sort": [
{
"account_number": {
"order": "desc"
}
}
]
}
参数名称描述
query 定义如何查询
match_all 查询类型,代表查询所有的所有,ES 可以在 query 中组合非常多的查询类型以完成复杂查询
from 完成分页查询功能
size 完成分页查询功能
sort 排序,多字段排序,会在前序字段相等时使用后续字段排序,否则以前序为准

返回部分字段

  • 查询前 5 条记录,并只返回 agebalance 字段。
1
2
3
4
5
6
7
8
9
10
11
12
GET /bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"_source": [
"age",
"balance"
]
}

match 匹配查询

提示

  • 使用 match 匹配字符串类型字段的时候,ES 会进行全文检索,并且每条记录都有相关性得分。
  • ES 进行全文检索时,底层使用的是倒排索引(Inverted Index)。
  • 基本类型(非字符串),精确匹配。查询 account_number 是 20 的所有记录。
1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"match": {
"account_number": 20
}
}
}
  • 字符串类型,精确匹配(使用 keyword 关键字)。查询出 address990 Mill Road 的所有记录,并给出相关性得分。
1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"match": {
"address.keyword": "990 Mill Road"
}
}
}
  • 字符串类型,单个单词(全文检索)。查询出 address 字段中包含 mill 单词的所有记录,并给出相关性得分。
1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"match": {
"address": "mill"
}
}
}
  • 字符串类型,多个单词(分词 + 全文检索)。查询出 address 字段中包含 mill 或者 road 或者 mill road 的所有记录,并给出相关性得分。
1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"match": {
"address": "mill road"
}
}
}

match_phrase 短语匹配

将需要匹配的值当成一个整体单词(不分词)进行检索

1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "mill road"
}
}
}

查出 address 字段中包含 mill road 的所有记录,不进行分词处理,并给出相关性得分。

multi_match 多字段匹配

1
2
3
4
5
6
7
8
9
10
11
12
GET /bank/_search
{
"query": {
"multi_match": {
"fields": [
"state",
"address"
],
"query": "mill"
}
}
}

查出 state 或者 address 字段中包含 mill 的所有记录。如果匹配的是包含多个单词的字符串(例如 mill road),会进行分词处理,并给出相关性得分。

bool 复合查询

bool 用来做复合查询。复合语句可以合并任何其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

类型描述
must 子句 (查询) 必须出现在匹配的文档中,并将有助于得分。
filter 子句 (查询) 必须出现在匹配的文档中。然而,与 must 不同的是,查询的分数将被忽略。过滤器子句在过滤器上下文中执行,这意味着评分被忽略,子句被考虑用于缓存。
should 子句 (查询) 应该出现在匹配的文档中。在 bool 查询中不包含 must 或者 filter 子句时,一个或多个 should 子句必须有相匹配的文档。
must_not 子句 (查询) 不能出现在匹配的文档中。子句在过滤器上下文中执行,这意味着评分被忽略,子句被考虑用于缓存。因为评分会被忽略,所以会返回所有文档的评分为 0。
must 复合查询

must 复合查询,表示必须满足列举的所有条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "M"
}
}
]
}
}
}
should 复合查询

should 复合查询,表示应该满足列举的条件。如果满足会增加相关性的评分,但不会改变查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会被作为默认匹配条件而去改变查询结果。

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
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "M"
}
}
],
"should": [
{
"match": {
"address": "lane"
}
}
]
}
}
}
must_not 复合查询

must_not 复合查询,表示必须不满足列出的所有条件。

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
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "M"
}
}
],
"should": [
{
"match": {
"address": "lane"
}
}
],
"must_not": [
{
"match": {
"email": "baluba.com"
}
}
]
}
}
}
filter 复合查询

filter 复合查询,作用与 must 一样,可用于查询结果过滤。除此之外,filter 还可以用于忽略相关性得分。

  • filter 可以与 mustshouldmust_not 复合查询一起使用,此时的作用是查询结果过滤,会产生相关性得分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
}
],
"filter": {
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}
}
}
}
}
}
  • filter 单独使用时,查询结果中不会产生相关性得分。值得一提的是,并不是所有的查询都需要产生相关性得分,特别是那些仅用于 filtering(过滤) 的文档。为了不计算得分,ES 会自动检查场景并且优化查询的执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}
}
}
}
}
}

term 匹配查询

term 的作用和 match 一样,用于匹配某个字段的值。全文检索字段(字符串类型)用 match,其他非字符串类型字段的匹配用 term

1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"term": {
"account_number": 20
}
}
}
  • term 可以结合 bool 复合查询一起使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"age": {
"value": 28
}
}
},
{
"match": {
"address": "990 Mill Road"
}
}
]
}
}
}

aggregations 聚合查询

聚合查询提供了从数据中分组和提取数据的能力,最简单的聚合方法大致等于 SQL GROUP BY 和 SQL 聚合函数。在 ES 中,有执行搜索返回 hits(命中结果),并且同时返回聚合结果,把一个响应中的所有 hits(命中结果)分隔开的能力。这是非常强大且有效的,可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的 API 来避免网络往返。

  • 执行聚合查询的语法如下
1
2
3
4
5
6
7
"aggs": {
"aggs_name": { // aggs_name - 聚合的名称,方便展示在结果集中
"agg_type": { // agg_type - 聚合的类型(avg、terms、max、min、sum 等等)

}
}
}
使用案例一

查询 address 中包含 mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情。

提示

下述 Query DSL 中的 size:0,表示不显示查询结果的数据,只显示聚合结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /bank/_search
{
"query": {
"match": {
"address": "mill"
}
},
"aggs": {
"group_by_age": {
"terms": {
"field": "age"
}
},
"avg_age": {
"avg": {
"field": "age"
}
}
},
"size": 0
}

返回的聚合查询结果如下:

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
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"avg_age": {
"value": 34.0
},
"group_by_age": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 38,
"doc_count": 2
},
{
"key": 28,
"doc_count": 1
},
{
"key": 32,
"doc_count": 1
}
]
}
}
}
使用案例二

查出所有年龄的分布,并且获取这些年龄段的这些人的平均薪资。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_avg": {
"terms": {
"field": "age",
"size": 1000
},
"aggs": {
"avg_banlance": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 1000
}
使用案例三

查出所有年龄的分布,并且获取这些年龄段中男性与女性的平均薪资,以及这些年龄段的这些人的平均薪资。

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
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_agg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"gender_agg": {
"terms": {
"field": "gender.keyword",
"size": 100
},
"aggs": {
"avg_banlance": {
"avg": {
"field": "balance"
}
}
}
},
"avg_banlance": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 1000
}