Elasticsearch文档查询
- Elasticsearch快速入门 第1篇:Elasticsearch入门
- Elasticsearch快速入门 第2篇:Elasticsearch和Kibana安装
- Elasticsearch快速入门 第3篇:Elasticsearch索引和文档操作
- Elasticsearch快速入门 第4篇:Elasticsearch文档查询
简单数据集
到目前为止,已经了解了基本知识,现在我们尝试用更逼真的数据集,这儿已经准备好了一份虚构的JSON,关于客户银行账户信息的。每个文档的结构如下:
{ "account_number": 0, "balance": 16623, "firstname": "Bradshaw", "lastname": "Mckenzie", "age": 29, "gender": "F", "address": "244 Columbus Place", "employer": "Euron", "email": "bradshawmckenzie@euron.com", "city": "Hobucken", "state": "CO" }
出于好奇,我从www.json-generator.com/生成了这些数据,请忽略数据的实际值和语义,因为这些都是随机生成的。
加载样本数据集
可以从这里下载示例数据集(accounts.json),解压到当前目录,然后用以下方式把它加载到集群中
curl -H "Content-Type: application/json" -XPOST 'localhost:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json" curl 'localhost:9200/_cat/indices?v'
返回内容如下:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size yellow open .kibana XYZPR5XGQGWj8YlyZ1et_w 1 1 1 0 3.1kb 3.1kb yellow open bank uoTQIb3GSDOH08CmsIy66A 5 1 999 0 639.5kb 639.5kb
这意味着我们已经成功批量索引999个文档到bank索引下(类型为account )。
注意,上面的操作不能在kibana中执行,需要使用curl
具体操作是,把下载的json文档放在和curl.exe相同的目录,然后打开命令提示符定位到curl.exe所在目录,然后粘贴以下命令(我的curl版本是7.53.1,需要改成下面的方式才能执行成功),回车即可
curl -H "Content-Type: application/json" -XPOST localhost:9200/bank/account/_bulk?pretty --data-binary "@accounts.json"
查询API
运行查询有两种方式,一是通过 REST request URI 方式发送查询参数,二是通过 REST request body 。方式二更为灵活,可以使用可读性好的JSON 格式定义你的查询条件,下面我们针对方式一举个例子,以后的教程都使用方式二。
REST API的查询条件放在_search之后,以下例子返回 bank 索引中的所有文档:
GET /bank/_search?q=*&sort=account_number:asc&pretty
bank 表示查询bank索引中的文档, _search 后面跟的是查询条件,q=* 参数指示 Elasticsearch 匹配索引中的所有文档。 sort=account_number:asc
参数指示使用 account_number 对结果进行升序排序。 pretty 参数告诉 Elasticsearch 返回漂亮的JSON结果。
返回部分内容如下:
{ "took" : 63, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1000, "max_score" : null, "hits" : [ { "_index" : "bank", "_type" : "account", "_id" : "0", "sort": [0], "_score" : null, "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"} }, { "_index" : "bank", "_type" : "account", "_id" : "1", "sort": [1], "_score" : null, "_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
- 告诉我们查询了多少个分片,以及查询成功/失败的分片数量hits
- 查询结果hits.total
- 符合我们查询条件的文档总数hits.hits
- 实际查询结果数组(默认为前10个文档)hits.sort
- 对结果进行排序的键(如果没提供,则默认使用_score进行排序)hits._score
和max_score
-现在先忽略这些字段
使用方式二执行上面查询如下
GET /bank/_search { "query": { "match_all": {} }, "sort": [ { "account_number": "asc" } ] }
不同点是我们用json格式的请求体代替了_search api uri中的q=*参数。我们将会在后面的内容讨论json格式的查询。
注意,当我们接收到返回结果的时候, elasticsearch 已经完全处理了这个请求,不会维护任何的服务器端的资源或者在结果中打开游标。这与许多其他的平台形成鲜明的对比(比如sql的游标)
查询语言介绍
Elasticsearch t提供了一种 JSON-style 的特定领域语言用来执行查询,称为 Query DSL,该查询语言十分全面,初看可能觉得有点吓人。事实上,学习它的最好方式就是从几个基本的例子开始。回到上一个例子,我们执行了这个查询:
GET /bank/_search { "query": { "match_all": {} } }
上面的 query 部分告诉我们查询定义是什么, match_all 部分仅仅是我们想运行的查询的类型,也就是查询指定索引下的所有文档。
除了查询参数以外,也可以通过其他参数影响查询结果。比如前面的 sort 指定排序字段,下面通过 size 指定返回结果数
GET /bank/_search { "query": { "match_all": {} }, "size": 1 }
注意 size 如果不指定,默认是10。
下面的例子匹配所有,并且返回第11到20之间的文档
GET /bank/_search { "query": { "match_all": {} }, "from": 10, "size": 10 }
from参数(最小值是0,不是1)指定返回文档的起始文档的索引, size 参数指定一共返回多少个文档。这个特性对实现分页非常有用。如果 from 没有指定,默认值是0。
下面的例子匹配所有,并且通过 balance 字段对结果进行降序排序,返回前10条(默认 size )文档。
GET /bank/_search { "query": { "match_all": {} }, "sort": { "balance": { "order": "desc" } } }
执行查询
接下来我们进一步探讨Query DSL。首先看一下返回的文档字段。默认情况下,完整的JSON文档作为所有搜索的一部分返回。
默认情况下,完整的JSON文档作为所有搜索的一部分返回。文档原始内容被称为源(查询结果中的_source字段
)。如果不希望返回整个源文档,也可以请求仅几个字段被返回。
以下示例显示如何返回两个字段(_source内
), account_number 和 balance :
GET /bank/_search { "query": { "match_all": {} }, "_source": ["account_number", "balance"], "size": 1 }
返回内容如下:
{ "took": 3, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 999, "max_score": 1, "hits": [ { "_index": "bank", "_type": "account", "_id": "25", "_score": 1, "_source": { "account_number": 25, "balance": 40540 } } ] } }
以上的例子仅仅减少了 _source 里的字段,返回的字段 account_number 和 balance 仍然包含在 _source 中
如果你之前有SQL背景,上述在概念上与SQL SELECT FROM
字段列表有些相似。
现在来看看查询部分。通过前面的示例,我们已经学会了如何使用 match_all 查询来匹配所有文档。现在介绍一个名为match
查询的新查询,可以将其视为基本的字段搜索查询(即针对特定字段或一组字段进行搜索)。
以下示例返回的 account_number 为20:
GET /bank/_search { "query": { "match": { "account_number": 20 } } }
返回结果:
{ "took": 15, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "bank", "_type": "account", "_id": "20", "_score": 1, "_source": { "account_number": 20, "balance": 16418, "firstname": "Elinor", "lastname": "Ratliff", "age": 36, "gender": "M", "address": "282 Kings Place", "employer": "Scentric", "email": "elinorratliff@scentric.com", "city": "Ribera", "state": "WA" } } ] } }
以下实例返回 address 中包含"mill"的所有账户:
GET /bank/_search { "query": { "match": { "address": "mill" } } }
以下示例返回address中包含"mill"或者"lane"的所有账户:
GET /bank/_search { "query": { "match": { "address": "mill lane" } } }
以下示例是match
(match_phrase
)的一个变体,返回在地址中包含短语"mill lane"的所有帐户:
GET /bank/_search { "query": { "match_phrase": { "address": "mill lane" } } }
下面介绍bool
(ean) query 。 布尔
查询允许我们把多个 match 查询合并到一个查询中。
以下示例由两个 match 查询组成,返回 address 中既包含"mill" 又包含"lane" 的所有账户:
GET /bank/_search { "query": { "bool": { "must": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } } }
在上面的示例中, bool must 里面的所有查询条件必须都为真时才会被匹配。
相比之下,下面的示例由两个match
查询组成,并返回在地址中包含"mill"或"lane"的所有帐户:
GET /bank/_search { "query": { "bool": { "should": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } } }
在上面的例子中, bool should 子句指定了一个查询列表,只要其中一个查询为真,文档就会被匹配。
以下示例由两个match
查询组成,并返回在地址中既不包含"mill"也不包含"lane"的所有帐户:
GET /bank/_search { "query": { "bool": { "must_not": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } } }
在上面的例子中,bool must_not 子句指定一个查询列表,只有查询列表中的条件都为假的时候才会被匹配。
也可以把 must,should,must_not 同时组合到bool
子句。此外,我们也可以组合bool
到任何一个bool
子句中,实现复杂的多层bool
子句嵌套逻辑。
下面的例子返回所有年龄是40岁但不居住在ID(Idaho)的账户:
GET /bank/_search { "query": { "bool": { "must": [ { "match": { "age": "40" } } ], "must_not": [ { "match": { "state": "ID" } } ] } } }
执行过滤
前面我们跳过了一点细节,文档得分(也就是在搜索结果中的 _score 字段)。分数是一个数值,它是文档与我们指定的搜索查询匹配的相对度量。分数越高,文档越相关,分数越低,文档的相关性越低。但查询并不总是需要产生分数,特别是当它们仅用于"过滤"文档集时。 Elasticsearch 会检测这些情况,并自动优化查询执行,以免计算无用的分数。
bool
查询支持filter子句,它允许你使用一个查询语句去限制其它子句的匹配结果,同时不会计算文档的得分。例如,我们来介绍一下
range
query, 它允许我们通过一个范围值去过滤文档。通常用于数字或日期过滤。
以下示例使用布尔查询返回余额在20000到30000之间(包括端值)的所有帐户。换句话说,我们想找到余额大于或等于20000且小于等于30000的账户。
GET /bank/_search { "query": { "bool": { "must": { "match_all": {} }, "filter": { "range": { "balance": { "gte": 20000, "lte": 30000 } } } } } }
仔细分析上面的例子,bool
查询包含了一个match_all
查询(查询部分)和一个range
查询(过滤部分)。我们也可以用任何其它的查询语句代替查询和过滤部分的语句。对于上面的例子,因为所有文档都是指定范围之内的,他们从某种意义上来说是等价的(equally),即他们的相关度都是一样的(filter子句查询,不会改变得分)。
除了 match_all
,match
,bool
,range
查询,还有很多种类的查询,但我们不在这里一一介绍。从现在开始,我们对查询已经有一个基础的了解,把学到的知识应用到其他查询类型应该也没什么难度。
执行聚合
聚合提供从数据中分组和提取统计信息的功能。理解聚合的最简单的方法是将其大致等同于SQL GROUP BY和SQL聚合函数。在 Elasticsearch 中,可以返回匹配搜索的同时返回聚合结果,在一个响应中将所有匹配的结果和聚合结果同时返回。这是非常强大和高效的,可以降低网络请求的次数。
以下示例通过state
字段进行分组,并按照count 降序排序,返回前10(默认值)条数据:
GET /bank/_search { "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword" } } } }
在SQL中,上述聚合在概念上类似于:
SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC
返回内容(仅部分)如下:
{ "took": 50, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 999, "max_score": 0, "hits": [] }, "aggregations": { "group_by_state": { "doc_count_error_upper_bound": 20, "sum_other_doc_count": 770, "buckets": [ { "key": "ID", "doc_count": 27 }, { "key": "TX", "doc_count": 27 }, { "key": "AL", "doc_count": 25 }, { "key": "MD", "doc_count": 25 }, { "key": "TN", "doc_count": 23 }, { "key": "MA", "doc_count": 21 }, { "key": "NC", "doc_count": 21 }, { "key": "ND", "doc_count": 21 }, { "key": "MO", "doc_count": 20 }, { "key": "AK", "doc_count": 19 } ] } } }
可以看到,有27个账户居住在ID(Idaho),27个账户居住在TX(Texas),25个账户居住在AL(Alabama)等等。
注意,设置size=0
是为了不显示搜索结果,因为我们仅仅想看返回的聚合结果。
基于上述例子,下面的例子除了分组还会计算每个州的账户的平均余额:
GET /bank/_search { "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword" }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } }
返回内容(仅部分)如下:
{ "took": 32, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 999, "max_score": 0, "hits": [] }, "aggregations": { "group_by_state": { "doc_count_error_upper_bound": 20, "sum_other_doc_count": 770, "buckets": [ { "key": "ID", "doc_count": 27, "average_balance": { "value": 24368.777777777777 } }, { "key": "TX", "doc_count": 27, "average_balance": { "value": 27462.925925925927 } }, { "key": "AL", "doc_count": 25, "average_balance": { "value": 25739.56 } }, { "key": "MD", "doc_count": 25, "average_balance": { "value": 24963.52 } }, { "key": "TN", "doc_count": 23, "average_balance": { "value": 29796.782608695652 } }, { "key": "MA", "doc_count": 21, "average_balance": { "value": 29726.47619047619 } }, { "key": "NC", "doc_count": 21, "average_balance": { "value": 26785.428571428572 } }, { "key": "ND", "doc_count": 21, "average_balance": { "value": 26303.333333333332 } }, { "key": "MO", "doc_count": 20, "average_balance": { "value": 24151.8 } }, { "key": "AK", "doc_count": 19, "average_balance": { "value": 24088.63157894737 } } ] } } }
注意我们是如何把average_balance聚合嵌入到group_by_state聚合中的。在所有的聚合中这是一种普遍的模式。你可以按你的需求随意的在聚合中嵌套聚合子句,汇总你的数据。
基于上面的例子,以下示例加入了按每个州的账户平均余额降序排序:
GET /bank/_search { "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword", "order": { "average_balance": "desc" } }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } }
以下示例演示了如何按年龄段(20-29岁,30-39岁和40-49岁),然后按性别分组,然后最终得到每个年龄段的男女平均账户余额:
GET /bank/_search { "size": 0, "aggs": { "group_by_age": { "range": { "field": "age", "ranges": [ { "from": 20, "to": 30 }, { "from": 30, "to": 40 }, { "from": 40, "to": 50 } ] }, "aggs": { "group_by_gender": { "terms": { "field": "gender.keyword" }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } } } }
返回结果如下:
{ "took": 21, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 999, "max_score": 0, "hits": [] }, "aggregations": { "group_by_age": { "buckets": [ { "key": "20.0-30.0", "from": 20, "to": 30, "doc_count": 450, "group_by_gender": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "M", "doc_count": 231, "average_balance": { "value": 27400.982683982686 } }, { "key": "F", "doc_count": 219, "average_balance": { "value": 25341.260273972603 } } ] } }, { "key": "30.0-40.0", "from": 30, "to": 40, "doc_count": 504, "group_by_gender": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "F", "doc_count": 253, "average_balance": { "value": 25670.869565217392 } }, { "key": "M", "doc_count": 251, "average_balance": { "value": 24288.239043824702 } } ] } }, { "key": "40.0-50.0", "from": 40, "to": 50, "doc_count": 45, "group_by_gender": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "M", "doc_count": 24, "average_balance": { "value": 26474.958333333332 } }, { "key": "F", "doc_count": 21, "average_balance": { "value": 27992.571428571428 } } ] } } ] } } }
还有许多其他聚合功能,将不再详细介绍。如果您想进一步试验,聚合参考指南是一个很好的起点。
结论
elasticsearch 是一个既简单又复杂的产品。目前为止,我们已经学了如何使用REST API以及 elasticsearch 的基本概念和特性。希望这个教程可以让你很好的理解 elasticsearch ,更重要的是,激励你继续学习后续教程要介绍的强大特性。
官方文档
https://www.elastic.co/guide/en/elasticsearch/reference/current/_exploring_your_data.html
参考文档
https://github.com/13428282016/elasticsearch-CN/wiki/es-gettting-started