Elasticsearch学习笔记(3)

目录

探索你的数据

现在我们已经了解了基础知识,让我们尝试处理一个更真实的数据集。

加载示例数据集

我们可以从这里下载数据集。将它下载保存到我们的当前目录并且加载到我们的集群里,如下:

curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_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索引(在_doc类型下)。

搜索API

现在让我们从简单的搜索开始。这里有两个基本的办法去运行搜索:其一是通过REST request URI发送搜索参数,另一个是通过REST request body发送搜索参数。请求体方法允许您更有表现力,也可以用更可读的JSON格式定义搜索。我们将尝试请求URI方法的一个示例,但是对于本教程的其余部分,我们将只使用请求主体方法。

搜索的REST API可以从_search端点访问。这个示例返回银行索引中的所有文档:

curl -X GET "localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty"

让我们首先分析搜索调用。我们在bank索引中搜索(_search端点),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" : 1000,
    "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":"[email protected]","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":"[email protected]","city":"Brogan","state":"IL"}
    }, ...
    ]
  }
}

关于响应,我们看到以下部分:

  • took - Elasticsearch执行搜索的时间以毫秒为单位
  • time_out - 告诉我们这个搜索是否超时
  • _shards - 告诉我们搜索了多少碎片,以及成功/失败搜索碎片的计数
  • hist - 搜索结果
  • hits.total - 符合我们的搜索条件的文档总数
  • hits.hits - 实际的搜索结果数组(默认为前10个文档)
  • hits.sort - 结果的排序键(如果按分数排序,则会丢失)
  • hits._score and max_score - 暂时忽略这些字段

下面是使用替代请求主体方法进行的相同搜索:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}
'

这里的区别在于,我们没有在URI中传递q=*,而是向_search API提供json风格的查询请求体。我们将在下一节讨论这个JSON查询。
很重要的一点:一旦返回搜索结果,Elasticsearch就完全完成了对请求的处理,不会在结果中维护任何类型的服务器端资源或打开游标。这与许多其他平台如SQL形成鲜明对比,你最初可能会得到查询结果的部分子集,然后如果你想获取其余的结果,必须使用某种状态的服务器端游标不断到服务器去获取。

介绍查询语言

Elasticsearch提供了一种json风格的域特定语言,您可以使用这种语言执行查询。这称为DSL查询语言。查询语言非常全面,乍一看可能有些吓人, 但实际上最好的学习办法是从几个基本示例开始。
回到上一个例子,我们执行了这个查询:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} }
}
'

分析上面,query部分告诉我们查询定义是什么,match_all部分只是我们想要运行的查询类型。match_all查询只是在指定索引中搜索所有文档。

除了查询参数外,我们还可以传递其他参数来影响搜索结果。在上面部分的例子中,我们传递了sort,这里我们传递了size

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "size": 1
}
'

注意,如果没有指定大小,默认值为10。
这个示例执行match_all并返回文档10到19:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}
'

from参数(基于0)指定从哪个文档索引开始,size参数指定从from参数开始返回多少文档。这个特性在实现搜索结果分页时非常有用。注意,如果未指定from,则默认为0。

这个示例执行match_all,并按照帐户余额降序对结果进行排序,并返回前10个(默认大小)文档。

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "sort": { "balance": { "order": "desc" } }
}
'
执行搜索

现在我们已经看到了一些基本的搜索参数,让我们更深入一些到DSL查询语句中。首先让我们看一下返回的文档字段。默认情形下,完整的JSON文档是返回的所有查询结果的一部分。这称为源(搜索命中的_source字段)。如果我们不想整个的源文档返回,我们能够从源文档中请求只返回几个字段。
这个示例展示了如何从查询中返回两个字段,account_numberbalance(在_source字段中):

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "_source": ["account_number", "balance"]
}
'

注意,上面的示例只是减少了_source字段。它仍然只返回一个名为_source的字段,但其中只包含account_number和balance字段。
如果你有SQL的背景,上面的查询在概念上是和SQL语言,SELECT FROM 字段列表有点相似的。
现在让我们继续查询部分,之前,我们已经看到match_all是如何被用来匹配所有文档的。现在让我们介绍一个新的查询叫做match query,可以将其视为基本的字段搜索查询(即针对特定字段或字段集进行的搜索)。
这个示例返回编号为20的帐户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "account_number": 20 } }
}
'

这个示例返回地址中包含术语“mill”的所有帐户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "address": "mill" } }
}
'

这个示例返回地址中包含术语“mill”或“lane”的所有帐户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "address": "mill lane" } }
}
'

这个示例是match (match_phrase)的变体,它返回地址中包含短语“mill lane”的所有帐户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": { "match_phrase": { "address": "mill lane" } }
}
'

现在让我们介绍bool query。bool查询允许我们使用布尔逻辑将较小的查询组合成较大的查询。
这个示例组合两个match查询并且返回地址中包含“mill”和“lane”的所有账户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}
'

在上面的示例中,bool must子句指定所有查询,这些查询必须为true,才能将文档视为匹配。
与此相反,这个示例包含两个匹配查询,并返回地址中包含“mill”或“lane”的所有帐户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}
'

在上面的示例中,bool should子句指定了一个查询列表,其中一个查询列表必须为true,才能将文档视为匹配。
这个示例包含两个匹配查询,并返回地址中既不包含“mill”也不包含“lane”的所有帐户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}
'

在上面的例子中,bool must_not子句指定了一个查询列表,其中没有一个查询必须为真,才能将文档视为匹配。

我们可以在bool查询中同时组合must、should和must_not子句。
此外,我们可以在任何bool子句中编写bool查询,以模拟任何复杂的多级布尔逻辑。
这个例子返回了所有40岁但不生活在ID(aho)中的人的账户:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}
'
执行过滤器

在上一节中,我们跳过了一个名为文档分数(搜索结果中的_score字段)的小细节。分数是一个数值,它是文档与我们指定的搜索查询匹配程度的相对度量。分数越高,文档越相关,分数越低,文档越不相关。但是查询并不总是需要产生分数,特别是当它们仅用于“过滤”文档集时。Elasticsearch可以检测到这些情况,并自动优化查询执行,以避免计算无用的分数。我们上一节介绍的bool query也支持filter子句,它允许我们使用一个查询来限制由其他子句匹配的文档,不改变如何计算分数。作为一个例子,让我们引入range query,它允许我们通过一系列值筛选文档。这通常用于数字或日期过滤。

这个示例使用bool查询返回所有余额在20000到30000之间的帐户(包括在内)。换句话说,我们希望找到的账户余额大于等于20000,小于等于30000。

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}
'

分析上面的语句,bool查询包含一个match_all子查询(查询部分)和一个range子查询(过滤部分)。我们可以代替任意其他的查询到这个查询部分和过滤部分。在上面的例子中,范围查询非常有意义,因为属于范围的所有文档都“相等”匹配,也就是说, 没有哪个文档是比其他文档更相关的。

除了match_allmatchboolrange查询之外,还有许多其他的查询类型可供使用,我们在这里不深入讨论。由于我们已经对它们的工作原理有了基本的了解,因此在学习和试验其他查询类型时应用这些知识应该不会太难。

执行聚合

聚合提供了从你的数据中分组和提取统计信息的能力。关于聚合最简单的理解方式就是将其大致等同于SQL GROUP BY和SQL聚合函数。在Elasticsearch中,您可以执行返回命中的搜索,同时在一个响应中返回与命中分离的聚合结果。这在某种意义上非常强大和高效,因为您可以运行查询和多个聚合,并一次性获得两个(或两个)操作的结果,从而避免使用简洁和简化的API进行网络往返。

首先,这个示例按状态对所有帐户进行分组,然后返回按计数递减排序的前10个(默认)状态:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "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" : 1000,
    "max_score" : 0.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" : "ME",
        "doc_count" : 20
      }, {
        "key" : "MO",
        "doc_count" : 20
      } ]
    }
  }
}

我们可以看到ID(爱达荷州)有27个账户,紧跟着TX(德克萨斯州)有27个账户,紧跟着AL(阿拉巴马州)有25个账户,等等。

注意:我们设置size=0来不展示搜索结果因为我们只想在返回内容里看到聚合结果。

在前面的聚合基础上,本示例计算了按状态排序的平均帐户余额(同样,仅计算按计数降序排序的前10个状态):

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}
'

注意我们如何将average_balance聚合嵌套到group_by_state聚合中。这是所有聚合的常见模式。您可以将聚合嵌套在聚合中,任意地从数据中提取需要的旋转汇总。

在前面的聚合的基础上,现在让我们按降序对平均余额进行排序:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "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岁)来分组,然后根据性别分组,最后得到平均账户余额,每个年龄等级,每个性别:

curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
  "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"
              }
            }
          }
        }
      }
    }
  }
}
'

还有很多其他的聚合功能我们在这里不会详细介绍。如果您想做进一步的实验,聚合参考指南是一个很好的起点。

结论

Elasticsearch是一个简单的和复杂的乘积。到目前为止,我们已经了解了它是什么,如何查看它的内部,以及如何使用一些REST api使用它。希望本教程能让您更好地理解Elasticsearch是什么,更重要的是,它激发了您对其其他强大功能的进一步试验!

下一章 —— Elasticsearch学习笔记(4)

你可能感兴趣的:(Elasticsearch学习笔记(3))