Elasticsearch - 基础教程
ES - 查询和聚合的基础使用
本文学习 Elasticsearch 基础的 API 使用以及使用官网提供的数据来操作学习:查询以及聚合操作
参考资料
Elasticsearch - 基础 API
在先前的文档中我们有说到 Elasticsearch 是在 Lucene 基础上,提供了 REST API 的操作接口,所以我们操作 Elasticsearch 的所有增删改查都是使用 GET、POST、PUT、DELETE
请求方式来区分
_cat 操作
# 查看所有节点
GET /_cat/indices
# 查看 es 健康状况
GET /_cat/health
# 查看主节点
GET /_cat/master
# 查看所有索引 - 类似 show database;
GET /_cat/indices
索引一个文档(保存)
保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识
PUT customer/_doc/1
- 在 customer 索引下的 _doc 类型下保存 1 号数据为
其中就是 type 是 _doc
,这是 ES 团队选择的一种废弃Type的方案,使用 _doc 后面可以无缝对接 API
# 添加一个文档、一般 put 用于修改
PUT customer/_doc/1
{
"name": "zenghr",
"age": 18,
"sex": 0
}
# post 会自动分配 id
POST customer/_doc
{
"name": "test",
"age": 18,
"sex": 0
}
返回结果
{
"_index" : "customer",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
PUT 和 POST 都可以
POST 新增:如果不指定 id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号
PUT 可以新增可以修改:PUT 必须指定 id;由于 PUT 需要指定 id,我们一般都用来做修改 操作,不指定 id 会报错
查询文档
GET customer/_doc/1
# 返回结果
{
"_index" : "customer",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "zenghr",
"age" : 18,
"sex" : 0
}
}
- _index: 在哪个索引
- _type: 在哪个类型
- _id: 记录 id
- _version: 版本号
- _seq_no: 并发控制字段,每次更新就会+1,用来做乐观锁
- _primary_term: 同上,主分片重新分配,如重启,就会变化
- _source: 真正的内容
- found: 数据是否存在
更新文档
# 修改文档
PUT customer/_doc/1
{
"name": "test",
"age": 18,
"sex": 0
}
# 或者
POST customer/_doc/1
{
"name": "test",
"age": 18,
"sex": 0
}
# 或者
POST customer/_doc/1/_update
{
"doc": {
"name": "test",
"age": 18,
"sex": 0
}
}
# 执行返回结果
{
"_index" : "customer",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
POST 和 PUT 的区别:
POST 操作: 会对比源文档数据,如果相同不会有什么操作,文档 version 不增加
PUT 操作: 总会将数据重新保存并增加 version 版本
带_update 对比元数据如果一样就不进行任何操作。 看场景:
对于大并发更新,不带 update;
对于大并发查询偶尔更新,带 update; 对比更新,重新计算分配规则
删除文档或索引
# 创建一个索引
PUT customer
# 删除文档
DELETE customer/_doc/1
# 删除一整个索引
DELETE customer
bulk 批量 API
在单个 API 调用中执行多个索引或删除操作,这减少了开销并且可以大大提高索引速度
POST customer/_doc/_bulk
{"index":{"_id":"2"}}
{"name":"test1","age":18,"sex":0}
{"delete":{"_id":"2"}}
{"index":{"_id":"3"}}
{"name":"test2","age":18,"sex":0}
注意
请注意 delete
动作不能有请求体,它后面跟着的是另外一个操作
行与行之间要以换行符相隔 \n
批量操作语法
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
...
学习准备:导入测试数据
提示
为了方便我们学习 Elasticsearch 的 API 操作,官方提供了测试数据,下面我们需要批量导入测试数据
下载测试数据
数据的 索引(Index) 是 bank,测试数据 JOSN 文件下载地址
数据的格式如下:
{"index": {"_id": "1"}}
{
"account_number": 1,
"balance": 39225,
"firstname": "Amber",
"lastname": "Duke",
"age": 32,
"gender": "M",
"address": "880 Holmes Lane",
"employer": "Pyrami",
"email": "amberduke@pyrami.com",
"city": "Brogan",
"state": "IL"
}
批量插入数据
查看测试数据的格式我们发现数据格式是 bulk 的数据语法,我们可以使用批量操作的 API _bulk
来插入数据
但是官方也提供了另外一种批量操作方式,可以直接导入 Json 文件,插入数据,这里我们使用导入文件的方式
将 accounts.json 拷贝至指定目录,我这里的目录是 Desktop/accounts.json
然后执行
curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@Desktop/accounts.json"
查询数据 - Kibana
为了方便测试,我们使用 kibana 的dev tool来进行学习测试:
进阶检索
ES 支持两种基本方式检索 :
- 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)
- 另一个是通过使用 REST request body 来发送它们(uri+请求体)
检索信息
搜索API的最基础的形式是没有指定任何查询的空搜索,它简单地返回集群中所有索引下的所有文档:
GET /bank/_doc/_search
# 请求参数方式检索
GET bank/_search?q=*&sort=account_number:asc
返回的结果像这样:
{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1000,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "bank",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"account_number" : 1,
"balance" : 39225,
"firstname" : "Amber",
"lastname" : "Duke",
"age" : 32,
"gender" : "M",
"address" : "880 Holmes Lane",
"employer" : "Pyrami",
"email" : "amberduke@pyrami.com",
"city" : "Brogan",
"state" : "IL"
}
},
// ...
]
}
}
相关字段解释
took
– Elasticsearch运行查询所花费的时间(以毫秒为单位)timed_out
–搜索请求是否超时_shards
- 搜索了多少个碎片,以及成功,失败或跳过了多少个碎片的细目分类。max_score
– 找到的最相关文档的分数hits.total.value
- 找到了多少个匹配的文档hits.sort
- 文档的排序位置(不按相关性得分排序时)hits._score
- 文档的相关性得分(使用match_all时不适用)hits.hits
- 实际的搜索结果数组(默认为前 10 的文档)
uri+请求体进行检索
GET bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": {
"order": "desc"
}
}
]
}
Query DSL
查询表达式(Query DSL)是一种非常灵活又富有表现力的 查询语言。 Elasticsearch 使用它可以以简单的 JSON 接口来展现 Lucene 功能的绝大部分
基本语法格式
要使用这种查询表达式,只需将查询语句传递给 query
参数:
GET /bank/_search
{
"query": YOUR_QUERY_HERE
}
空查询(empty search) —{}
— 在功能上等价于使用 match_all
查询, 正如其名字一样,匹配所有文档:
GET /bank/_search
{
"query": {
"match_all": {}
}
}
查询语句结构
一个查询语句的典型结构:
{
"query_name": {
"argument": value,
"argument": value,...
}
}
如果是针对某个字段,那么它的结构如下:
{
"query_name": {
"field_name": {
"argument": value,
"argument": value,...
}
}
}
举个例子,你可以使用 match
查询语句 来查询 bank 索引下 email
字段中包含 cedward.com
的 账号信息:
GET bank/_search
{
"query": {
"match": {
"email": "cedward.com"
}
}
}
- query 定义如何查询
- match_all 查询类型【代表查询所有的所有】,es 中可以在 query 中组合非常多的查
询类型完成复杂查询 - 除了 query 参数之外,我们也可以传递其它的参数以改变查询结果。如 sort,size
- from+size限定,完成分页功能
- sort排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准
返回部分字段
如果只想要返回部分的字段,可以使用 _source
指定字段
# 返回部分字段
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"_source": [
"age",
"balance"
]
}
match【匹配查询】
如果要在字段中搜索特定字词,可以使用match
- 全文检索
GET bank/_search
{
"query": {
"match": {
"email": "cedward.com"
}
}
}
最终查询出 email 中包含 cedward.com
单词的所有记录
match 当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分
- 多个单词 - 分词 + 全文检索
GET bank/_search
{
"query": {
"match": {
"email": "cedward.com pyrami.com"
}
}
}
最终查询出 email 中包含 cedward.com 或者 pyrami.com 或者 cedward.com pyrami.com 的所有记录,并给出相关性得分
match_phrase【短语匹配】
将需要匹配的值当成一个 整体单词(不分词
) 进行检索
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "mill lane"
}
}
}
查出 address 中包含 mill road 的所有记录,并给出相关性得分
multi_match【多字段匹配】
GET bank/_search
{
"query": {
"multi_match": {
"query": "mill",
"fields": [
"state",
"address"
]
}
}
}
state 或者 address 包含 mill
bool【复合查询】
bool 用来做复合查询:
复合语句可以合并 任何 其它查询语句,包括复合语句,了解这一点是很重要的。这就意味 着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑
- must - 必须达到must列举的所有条件
- should: 应该达到 should 列举的条件,如果达到会
增加相关文档的评分
,并不会改变 查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会 被作为默认匹配条件而去改变查询结果
query 只有 should 的情况
- must_not 必须不是指定的情况
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
},
{
"match": {
"gender": "M"
}
}
],
"should": [
{
"match": {
"address": "lane"
}
}
],
"must_not": [
{
"match": {
"email": "neteria.com"
}
}
]
}
}
}
address 包含 mill,并且 gender 是 M,如果 address 里面有 lane 最好不过,但是 email 必 须不包含 neteria.com
事件 | 描述 |
---|---|
must | 子句(查询)必须出现在匹配的文档中,并将有助于得分 |
filter | 子句(查询)必须出现在匹配的文档中,但它以不评分、过滤模式来进行,只是根据过滤标准来排除或包含文档 |
should | 子句(查询)应该出现在匹配的文档中,出现则得分,没有也不要紧 |
must_not | 子句(查询)不能出现在匹配的文档中 |
range【区间查询】
range
查询找出那些落在指定区间内的数字或者时间
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
被允许的操作符如下:
gt
- 大于gte
- 大于等于lt
- 小于lte
- 小于等于
filter【结果过滤】
并不是所有的查询都需要产生分数,特别是那些仅用于 "filtering"(过滤)的文档。为了不计算分数 Elasticsearch 会自动检查场景并且优化查询的执行
增加带过滤器(filtering)的查询
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
}
],
"should": [
{
"match": {
"address": "lane"
}
}
],
"filter": {
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}
}
}
}
}
}
过滤 balance 大于等于 10000 并且 小于等于 20000 的数据,并且address 包含 mill,如果 address 里面有 lane 最好不过
term 查询
和 match 一样。匹配某个属性的值。全文检索字段用 match,其他非 text 字段匹配用 term
term 查询被用于 精确值匹配
,这些精确值可能是数字、时间、布尔或者那些 not_analyzed
的字符串:
{ "term": { "age": 26 }}
{ "term": { "date": "2014-09-01" }}
{ "term": { "public": true }}
{ "term": { "tag": "full_text" }}
Aggregation【聚合查询】
提示
聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 SQL GROUP BY 和 SQL 聚合函数。在 Elasticsearch 中,您有执行搜索返回 hits(命中结果),并且同时返 回聚合结果,把一个响应中的所有 hits(命中结果)分隔开的能力。这是非常强大且有效的, 您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用 一次简洁和简化的 API 来避免网络往返
简单聚合
比如我们希望计算出account每个州的统计数量, 使用aggs
关键字对state
字段聚合,被聚合的字段无需对分词统计,所以使用state.keyword
对整个字段统计
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
}
}
}
}
因为无需返回条件的具体数据, 所以设置size=0,返回hits为空。
doc_count
表示bucket中每个州的数据条数
复杂查询 - 聚合排序
- 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资,并且通过聚合结果排序
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_avg": {
"terms": {
"field": "age",
"size": 1000,
"order": {
"banlances_avg": "desc"
}
},
"aggs": {
"banlances_avg": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 1000
}
- 查出所有年龄分布,并且这些年龄段中 M 的平均薪资和 F 的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_agg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"gender_agg": {
"terms": {
"field": "gender.keyword",
"size": 100
},
"aggs": {
"balance_avg": {
"avg": {
"field": "balance"
}
}
}
},
"balance_avg": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 1000
}