Elasticsearch7.2中文教程翻译(五):探索数据
探索您的数据
样本数据集
现在我们已经了解了基础知识,让我们尝试更真实的数据集。我准备了一份关于客户银行账户信息的虚构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/_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 bank l7sSYV2cQXmu6_4rJWVIww 5 1 1000 0 128.6kb 128.6kb
这意味着我们只是成功地将1000个文档批量索引到bank索引中。
搜索API
现在让我们从一些简单的搜索开始吧。有两种基本的搜索方式:
一种是通过发送搜索参数REST请求URI
另一种通过发送他们REST请求体。请求体方法允许您更具表现力,并以更可读的JSON格式定义搜索。
我们将尝试一个请求URI方法的示例,在本教程的其余部分,我们将专门使用请求体方法。
可以从_search端点访问用于搜索的REST API 。此示例返回银行索引中的所有文档:
GET /bank/_search?q=*&sort=account_number:asc&pretty
让我们分析一下搜索调用。我们正在bank索引中搜索(_search endpoint),
q=*参数表示Elasticsearch匹配索引中的所有文档;
sort=account_number:asc参数表示使用每个文档的account_number字段按升序对结果排序;
同样,pretty参数只告诉Elasticsearch返回JSON结果。
返回结果(部分显示):
{
"took" : 63,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value": 1000,
"relation": "eq"
},
"max_score" : null,
"hits" : [ {
"_index" : "bank",
"_type" : "_doc",
"_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" : "_doc",
"_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.total.value- 总命中数的值(必须在上下文中解释hits.total.relation)。
- hits.total.relation- 是否hits.total.value是确切的命中计数,在这种情况下它等于"eq"或总命中数的下限(大于或等于),在这种情况下它等于gte。
- hits.hits - 实际的搜索结果数组(默认为前10个文档)
- hits.sort - 为每个结果排序键的排序值(如果按分数排序则丢失)
- hits._score并max_score- 暂时忽略这些字段
精度hits.total由请求参数控制track_total_hits,当设置为true时,请求将准确跟踪总命中("relation": "eq")。默认为10,000 这意味着总命中数被准确地跟踪到10,000文档。您可以通过track_total_hits显式设置为true 来强制进行准确计数。有关详细信息,请参阅请求正文文档。
这里是使用请求体方法的请求,请求结果与上面的完全一致:
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
这里的不同之处在于,我们不是传入q=*URI
,而是向_searchAPI
提供JSON样式的查询请求体。我们将在下一节讨论这个JSON查询。
重要的是要理解,一旦您获得了搜索结果,Elasticsearch就完全完成了请求,并且不会在结果中维护任何类型的服务器端资源或打开游标。这与SQL等许多其他平台形成鲜明对比,其中您最初可能会预先获得查询结果的部分子集,然后如果要获取(或翻页)其余部分,则必须不断返回服务器使用某种有状态服务器端游标的结果。
(翻译成人话就是:Elasticsearch只返回10条数据,如果想分页或者查询更多数据需要自己写请求参数,ES不维护游标或者分页)
查询语言
Elasticsearch提供了一种JSON样式的特定于域的语言,可用于执行查询。这被称为查询DSL。查询语言非常全面,乍一看可能令人生畏,但实际学习它的最佳方法是从一些基本示例开始。
回到上一个例子,我们执行了这个查询:
GET /bank/_search
{
"query": { "match_all": {} }
}
解析上面的内容,该query部分告诉我们要查询什么,match_all是我们想要运行的查询类型。match_all是在指定索引的所有文件进行搜索。
除了query参数,我们还可以传递其他参数来影响搜索结果。在上面我们传入的部分的示例中 sort,我们传入size:
GET /bank/_search
{
"query": { "match_all": {} },
"size": 1
}
请注意,如果size未指定,则默认为10。
此示例执行 match_all并返回文档10到19条的数据:
GET /bank/_search
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}
from(默认从0开始)参数规定了该文件从多少条数据开始
size参数指定了返回多少文件。
在实现搜索结果的分页时,此功能非常有用。
请注意,如果from未指定,则默认为0。
此示例执行 match_all并按帐户余额降序对结果进行排序,并返回前10个(默认大小)文档。
GET /bank/_search
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}
搜索
现在我们已经看到了一些基本的搜索参数,让我们再深入研究一下Query DSL。我们先来看一下返回的文档字段。默认情况下,完整的JSON文档作为所有搜索的一部分返回。这被称为源(_source搜索命中中的字段)。如果我们不希望返回整个源文档,我们只能请求返回源中的几个字段。
此示例显示如何从搜索中返回两个字段account_number和balance(内部_source):
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
请注意,上面的示例只是简化了_source字段。它仍将返回一个名为_source字段,但在其中包含的字段只有account_number和balance。
如果您有SQL背景,则上面的概念与SQL SELECT FROM字段列表有些相似。
现在让我们转到查询部分。以前,我们已经看到match_all
查询如何用于匹配所有文档。现在让我们介绍一个名为match
查询的新查询,它可以被认为是一个基本的搜索查询(即针对特定字段或字段集进行的搜索)。
此示例返回编号为20的帐户:
GET /bank/_search
{
"query": { "match": { "account_number": 20 } }
}
此示例返回地址中包含“mill”的所有帐户:
GET /bank/_search
{
"query": { "match": { "address": "mill" } }
}
此示例返回地址中包含术语“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问题。该bool查询允许我们使用布尔逻辑将较小的查询组成更大的查询。
此示例组成两个match查询并返回地址中包含“mill”和“lane”的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的示例中,该bool must子句指定必须为true才能将文档视为匹配的所有查询。
相反,此示例组成两个match查询并返回地址中包含“mill”或“lane”的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的示例中,该bool should子句指定了一个查询列表,其中任何一个都必须为true才能使文档被视为匹配。
此示例组成两个match查询并返回地址中既不包含“mill”也不包含“lane”的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的示例中,该bool must_not子句指定了一个查询列表,对于要被视为匹配的文档,这些查询都不能为true。
我们可以在查询中同时组合must,should和must_not子句bool。此外,我们可以在任何这些bool子句中组合查询来模仿任何复杂的多级布尔逻辑。
此示例返回40岁但未居住在ID(aho)中的人的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
过滤器
在上一节中,我们跳过了一个名为document score(搜索结果中的_score字段)的小细节。分数是一个数值,它是衡量文档与我们指定的搜索查询匹配程度的一个相对指标。分数越高,文档越相关,分数越低,文档越不相关。
但是查询并不总是需要生成分数,特别是当它们只用于“过滤”文档集时。Elasticsearch检测这些情况并自动优化查询执行,以避免计算无用的分数。
我们在上一节中介绍的bool查询还支持一些filter子句,这些子句允许我们使用查询来限制将与其他子句匹配的文档,而不会更改计算分数的方式。作为示例,让我们介绍一下range查询,它允许我们按一系列值过滤文档。这通常用于数字或日期过滤。
此示例使用bool查询返回所有余额介于20000和30000之间的帐户。换句话说,我们希望找到余额大于或等于20000且小于或等于30000的帐户。
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
bool查询包含match_all查询(查询部分)和range查询(过滤部分)。我们可以将任何其他查询替换为查询和过滤器部分。在上面的情况下,范围查询非常有意义,因为落入范围的文档都“相同”匹配,即,没有文档比另一文档更相关。
除了match_all
,match
,bool
,和range
查询,有很多可用的其他查询类型的,我们不会进入他们在这里。由于我们已经基本了解它们的工作原理,因此将这些知识应用于学习和试验其他查询类型应该不会太困难。
聚合
聚合提供了从数据中分组和提取统计信息的功能。考虑聚合的最简单方法是将其大致等同于SQL GROUP BY和SQL聚合函数。在Elasticsearch中,您可以执行返回匹配的搜索,同时在一个响应中返回与命中相关的聚合结果。这是非常强大和高效的,因为您可以运行查询和多个聚合,并一次性获取两个(或任一)操作的结果,避免使用简洁和简化的API进行网络往返。
首先,此示例按状态对所有帐户进行分组,然后返回按计数降序排序的前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 LIMIT 10;
响应返回:
{
"took": 29,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped" : 0,
"failed": 0
},
"hits" : {
"total" : {
"value": 1000,
"relation": "eq"
},
"max_score" : null,
"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" : "ME",
"doc_count" : 20
}, {
"key" : "MO",
"doc_count" : 20
} ]
}
}
}
我们可以看到ID(Idaho)有27个账户,其次是TX(Texas)的27个账户,其次是AL(Alabama)的25个账户,依此类推。
请注意,我们设置size=0为不显示搜索匹配,因为我们只想在响应中看到聚合结果。
在前一个聚合的基础上,此示例按州计算平均帐户余额(同样仅针对按降序排序的前10个州):
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
注意,我们如何将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"
}
}
}
}
}
}
}
}
还有许多其他聚合功能,我们在这里不会详细讨论。如果您想做进一步的实验,那么聚合参考指南是一个很好的起点。
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »