1. 数据准备
1、创建索引映射mapping :
PUT /cars
{
"mappings": {
"properties": {
"price":{
"type": "integer"
},
"color":{
"type": "keyword"
},
"make":{
"type": "keyword"
},
"sold":{
"type": "date"
}
}
}
}
2、索引文档:
POST /cars/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
2. 过滤和聚合
2.1 过滤
如果我们想找到售价在 $10,000 美元之上的所有汽车同时也为这些车计算平均售价, 可以简单地使用一个 constant_score 查询和 filter 约束:
GET /cars/_search
{
"size": 0,
"query": {
"constant_score": {
"filter": {
"range": {
"price": {
"gte": 2000
}
}
}
}
},
"aggs": {
"avg_agg": {
"avg": {
"field": "price"
}
}
}
}
从根本上讲,使用 non-scoring 查询和使用 match 查询没有任何区别。查询(包括了一个过滤器)返回一组文档的子集,聚合正是操作这些文档。使用 filtering query 会忽略评分,并有可能会缓存结果数据等等。
{
"aggregations" : {
"avg_agg" : {
"value" : 26500.0
}
}
}
2.2 过滤桶
但是如果我们只想对聚合结果过滤怎么办? 假设我们正在为汽车经销商创建一个搜索页面, 我们希望显示用户搜索的结果,但是我们同时也想在页面上提供更丰富的信息,包括(与搜索匹配的)上个月度汽车的平均售价。
这里我们无法简单的做范围限定,因为有两个不同的条件。搜索结果必须是 ford ,但是聚合结果必须满足 ford AND sold > now - 1M 。
为了解决这个问题,我们可以用一种特殊的桶,叫做 filter 。 我们可以指定一个过滤桶,当文档满足过滤桶的条件时,我们将其加入到桶内。
GET /cars/_search
{
"size": 0,
"query": {
"match": {
"make": "ford"
}
},
"aggs": {
"recent_sales": {
"filter": {
"range": {
"sold": {
"from": "now-1M"
}
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
3. 多桶排序
多值桶( terms 、 histogram 和 date_histogram )动态生成很多桶。 Elasticsearch 是如何决定这些桶展示给用户的顺序呢?
默认的,桶会根据 doc_count 降序排列。这是一个好的默认行为,因为通常我们想要找到文档中与查询条件相关的最大值:售价、人口数量、频率。但有些时候我们希望能修改这个顺序,不同的桶有着不同的处理方式。
3.1 内置排序
这些排序模式是桶固有的能力:它们操作桶生成的数据 ,比如 doc_count 。 它们共享相同的语法,但是根据使用桶的不同会有些细微差别。
让我们做一个 terms 聚合但是按 doc_count 值的升序排序:
GET /cars/_search
{
"size": 0,
"aggs": {
"group_by_agg": {
"terms": {
"field": "color",
"order": {
"_count": "asc"
}
}
}
}
}
我们为聚合引入了一个 order 对象, 它允许我们可以根据以下几个值中的一个值进行排序:
_count :按文档数排序。对 terms 、 histogram 、 date_histogram 有效。_term :按词项的字符串值的字母顺序排序。只在 terms 内使用。_key :按每个桶的键值数值排序。 只在 histogram 和 date_histogram 内使用。
{
"aggregations" : {
"group_by_agg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "blue",
"doc_count" : 2
},
{
"key" : "green",
"doc_count" : 2
},
{
"key" : "red",
"doc_count" : 4
}
]
}
}
}
3.2 按度量排序
有时,我们会想基于度量计算的结果值进行排序。我们可能想按照汽车颜色创建一个销售条状图表,但按照汽车平均售价的升序进行排序。我们可以增加一个度量,再指定 order 参数引用这个度量即可:
GET /cars/_search
{
"size": 0,
"aggs": {
"group_by_agg": {
"terms": {
"field": "color",
"order": {
"avg_price": "asc"
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
{
"took" : 7,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 8,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_by_agg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "blue",
"doc_count" : 2,
"avg_price" : {
"value" : 20000.0
}
},
{
"key" : "green",
"doc_count" : 2,
"avg_price" : {
"value" : 21000.0
}
},
{
"key" : "red",
"doc_count" : 4,
"avg_price" : {
"value" : 32500.0
}
}
]
}
}
}
4. 近似聚合
如果所有的数据都在一台机器上,那么也就不需要像 Elasticsearch 这样的分布式软件了。不过一旦我们开始分布式存储数据,就需要小心地选择算法。
有些算法可以分布执行,到目前为止讨论过的所有聚合都是单次请求获得精确结果的。这些类型的算法通常被认为是高度并行的,因为它们无须任何额外代价,就能在多台机器上并行执行。 复杂的操作则需要在算法的性能和内存使用上做出权衡。对于这个问题,我们有个三角因子模型:大数据、精确性和实时性。我们需要选择其中两项:
- 精确 + 实时:数据可以存入单台机器的内存之中,我们可以随心所欲,使用任何想用的算法。结果会 100% 精确,响应会相对快速。
- 大数据 + 精确:传统的 Hadoop。可以处理 PB 级的数据并且为我们提供精确的答案,但它可能需要几周的时间才能为我们提供这个答案。
- 大数据 + 实时:近似算法为我们提供准确但不精确的结果。
Elasticsearch 目前支持两种近似算法( cardinality 和 percentiles )。 它们会提供准确但不是 100% 精确的结果。 以牺牲一点小小的估算错误为代价,这些算法可以为我们换来高速的执行效率和极小的内存消耗。
Elasticsearch 提供的首个近似聚合是 cardinality 度量。 它提供一个字段的基数,即该字段的 distinct 或者 unique 值的数目。 你可能会对 SQL 形式比较熟悉:
SELECT COUNT(DISTINCT color) FROM cars
去重是一个很常见的操作,可以回答很多基本的业务问题:
- 网站独立访客是多少?
- 卖了多少种汽车?
- 每月有多少独立用户购买了商品?
我们可以用 cardinality 度量确定经销商销售汽车颜色的数量:
GET /cars/_search
{
"size": 0,
"aggs": {
"distinct_color": {
"cardinality": {
"field": "color"
}
}
}
}
{
"aggregations" : {
"distinct_color" : {
"value" : 3
}
}
}
可以让我们的例子变得更有用:每月有多少颜色的车被售出?为了得到这个度量,我们只需要将一个 cardinality 度量嵌入一个 date_histogram :
GET /cars/_search
{
"size": 0,
"aggs": {
"month_agg": {
"date_histogram": {
"field": "sold",
"interval": "month",
"min_doc_count": 1
},
"aggs": {
"color_agg": {
"cardinality": {
"field": "color"
}
}
}
}
}
}
{
"aggregations" : {
"month_agg" : {
"buckets" : [
{
"key_as_string" : "2014-01-01T00:00:00.000Z",
"key" : 1388534400000,
"doc_count" : 1,
"color_agg" : {
"value" : 1
}
},
{
"key_as_string" : "2014-02-01T00:00:00.000Z",
"key" : 1391212800000,
"doc_count" : 1,
"color_agg" : {
"value" : 1
}
},
{
"key_as_string" : "2014-05-01T00:00:00.000Z",
"key" : 1398902400000,
"doc_count" : 1,
"color_agg" : {
"value" : 1
}
},
{
"key_as_string" : "2014-07-01T00:00:00.000Z",
"key" : 1404172800000,
"doc_count" : 1,
"color_agg" : {
"value" : 1
}
},
{
"key_as_string" : "2014-08-01T00:00:00.000Z",
"key" : 1406851200000,
"doc_count" : 1,
"color_agg" : {
"value" : 1
}
},
{
"key_as_string" : "2014-10-01T00:00:00.000Z",
"key" : 1412121600000,
"doc_count" : 1,
"color_agg" : {
"value" : 1
}
},
{
"key_as_string" : "2014-11-01T00:00:00.000Z",
"key" : 1414800000000,
"doc_count" : 2,
"color_agg" : {
"value" : 1
}
}
]
}
}
}
|