- 桶(bucket)聚合
- adjacency_matrix 聚合
- 使用
- Limitations
- auto_date_histogram(自动间隔的日期直方图聚合)
- 键(key)
- 间隔(interval)
- 时区(time zone)
- 脚本
- 参数 minimum_interval
- 缺失的值
- children 聚合
- composite(复合聚合)
- 值的来源(value source)
- terms (词项)
- histogram (直方图)
- date histogram (日期直方图)
- 混合不同的值源
- 排序 (order)
- 缺失的桶
- size
- 分页(pagination)
- 提前终止 (early termination)
- 子聚合
- 管道聚合(pipeline aggregations)
- 值的来源(value source)
- date_histogram(日期直方图聚合)
- 日历和固定间隔值
- 日历间隔
- 日历间隔的示例
- 固定间隔
- 固定间隔的示例
- 注意
- key
- 时区
- 时差(offset)
- keyed 响应
- 脚本
- 缺失的值
- 排序(order)
- 使用脚本按某一天是一周中的第几天进行聚合
- 日历间隔
- 日历和固定间隔值
- date_range(日期范围聚合)
- 缺失的值
- 日期格式/模式
- 日期范围聚合中的时区
- keyed 响应
- diversified_sampler(多样化取样聚合)
- 脚本化示例:
- shard_size
- max_docs_per_value
- execution_hint
- 局限性
- 不能在 breadth_first 聚合下嵌套
- 有限的去重逻辑
- geo/date 字段没有专门的语法
- filter(过滤器聚合)
- filters(多个过滤器聚合)
- 匿名过滤器
- 其他桶(other_bucket)
- geo_distance(地理距离聚合)
- keyed 响应
- geohash_grid(geohash网格聚合)
- 简单的低精度的请求
- 高精度的请求
- 带有附加边界框过滤的请求
- 赤道上的单元格的空间大小
- 选项
- adjacency_matrix 聚合
桶(bucket)聚合
桶(bucket)聚合不像度量(metrics)聚合那样计算字段的度量,而是创建文档的桶。 每个桶都与一个标准(取决于聚合类型)相关联,该标准确定当前上下文中的文档是否“落入”该桶中。 换句话说,桶有效地定义了文档的集合。 除了桶本身,bucket
聚合还计算并返回“落入”每个桶的文档数量。
与 metrics
聚合相反,桶聚合可以包含子聚合。 这些子聚合将为它们的“父”桶聚合创建的桶进行聚合。
有各种不同的桶聚合器,每个都有不同的“分桶”策略。 有些定义单个桶,有些定义固定数量的多个桶,还有一些在聚合过程中动态创建桶。
单个响应中允许的最大的桶数受动态集群设置
search.max_buckets
的限制。 它默认为 10,000,尝试返回超过此限制的请求将会失败并抛出异常。
adjacency_matrix 聚合
返回邻接矩阵(adjacency matrix)形式的桶聚合。 该请求提供了命名过滤器表达式的集合,类似于 filters
聚合请求。 响应中的每个桶代表交叉过滤器矩阵中的一个非空单元。
给定名为A
、B
和 C
的过滤器,响应将返回具有以下名称的桶:
A | B | C | |
---|---|---|---|
A | A | A&B | A&C |
B | B | B&C | |
C | C |
交叉的桶(例如 A&C
)使用由 & 符号分隔的两个过滤器名称的组合来标记。 请注意,响应不包括“C&A”桶,因为这是与“A&C”相同的一组文档。 因为矩阵是对称的(symmetric),所以我们只返回它的一半。 为此,我们对过滤器名称字符串进行排序,并始终使用一对名称中最小的一个作为"&"分隔符左侧的值。
如果客户端希望使用默认的&符号之外的分隔符字符串,可以在请求中传递一个替代的 separator
参数。
示例:
PUT /emails/_bulk?refresh
{ "index" : { "_id" : 1 } }
{ "accounts" : ["hillary", "sidney"]}
{ "index" : { "_id" : 2 } }
{ "accounts" : ["hillary", "donald"]}
{ "index" : { "_id" : 3 } }
{ "accounts" : ["vladimir", "donald"]}GET emails/_search
{"size": 0,"aggs" : {"interactions" : {"adjacency_matrix" : {"filters" : {"grpA" : { "terms" : { "accounts" : ["hillary", "sidney"] }},"grpB" : { "terms" : { "accounts" : ["donald", "mitt"] }},"grpC" : { "terms" : { "accounts" : ["vladimir", "nigel"] }}}}}}
}
在上面的例子中,我们分析了 email 消息,以查看哪些组的个人交换了消息。 我们将获得每个组的单独计数,以及记录了交互的组对的消息计数。
响应:
{"took": 9,"timed_out": false,"_shards": ...,"hits": ...,"aggregations": {"interactions": {"buckets": [{"key":"grpA","doc_count": 2},{"key":"grpA&grpB","doc_count": 1},{"key":"grpB","doc_count": 2},{"key":"grpB&grpC","doc_count": 1},{"key":"grpC","doc_count": 1}]}}
}
使用
这种聚合本身可以提供创建无向加权图所需的所有数据。 但是,当与子聚合(如date_histogram
)一起使用时,结果可以提供执行动态网络分析所需的额外级别的数据,在这种情况下,检查一段时间内的交互变得非常重要。
Limitations
对于N个过滤器,其所产生的桶的矩阵可以是 N²/2,因此存在100个过滤器的默认最大值。 这个设置可以通过修改索引级别的设置 index.max_adjacency_matrix_filters
来改变(注意:这个设置将在 8.0版本后废除)。
auto_date_histogram(自动间隔的日期直方图聚合)
一个类似于日期直方图聚合的多桶聚合,但不是靠给定的 间隔(interval) 来作为每个桶的宽度,而是给定目标桶的数量来指示所需的桶数,并且自动选择桶的间隔以用最好的方式来实现该目标。 返回的桶数将始终小于或等于该目标数。
buckets 字段是可选的,如果未指定,将默认为 10 个桶。
以10个桶为目标进行请求。
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"auto_date_histogram" : {"field" : "date","buckets" : 10}}}
}
键(key)
在内部,一个日期被表示为一个64位的数字-一个时间戳,以毫秒为单位。 这些时间戳作为桶的键(key)
返回。 key_as_string
是使用 format
参数指定的格式(format)转换为格式化的日期字符串的相同时间戳:
如果未指定 format
(格式),则它将使用字段映射中指定的第一个日期格式(format)。
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"auto_date_histogram" : {"field" : "date","buckets" : 5,"format" : "yyyy-MM-dd" }}}
}
- 支持表达性的日期格式模式
响应:
{..."aggregations": {"sales_over_time": {"buckets": [{"key_as_string": "2015-01-01","key": 1420070400000,"doc_count": 3},{"key_as_string": "2015-02-01","key": 1422748800000,"doc_count": 2},{"key_as_string": "2015-03-01","key": 1425168000000,"doc_count": 2}],"interval": "1M"}}
}
间隔(interval)
基于聚合收集的数据选择返回桶的间隔,使得返回的桶的数量小于或等于请求的数量。 返回的间隔可能是:
seconds | 以1、5、10和30的倍数 |
---|---|
minutes | 以1、5、10和30的倍数 |
hours | 以1、3和12的倍数 |
days | 以1和7的倍数 |
months | 以1和3的倍数 |
years | 以1、5、10、20、50和100的倍数 |
在最坏的情况下,对于请求的桶数,每日桶数太多,返回的桶数将是请求的桶数的1/7。
时区(time zone)
日期时间以 UTC 存储在 Elasticsearch 中。 默认情况下,所有的分桶和舍入也在 UTC 中完成。 time_zone
参数可用于指示分桶时应该使用不同的时区。
时区可以指定为 ISO 8601 UTC 时差(例如+01:00
或 -08:00
),也可以指定为时区id(在TZ数据库中使用的标识符),比如America/Los_Angeles
。
看下面这个例子:
#添加并索引3个文档
PUT my_index/log/1?refresh
{"date": "2015-10-01T00:30:00Z"
}PUT my_index/log/2?refresh
{"date": "2015-10-01T01:30:00Z"
}PUT my_index/log/3?refresh
{"date": "2015-10-01T02:30:00Z"
}#搜索
GET my_index/_search?size=0
{"aggs": {"by_day": {"auto_date_histogram": {"field": "date","buckets" : 3}}}
}
如果未指定时区则使用 UTC,从 UTC 时间的 2015年10月1日午夜开始返回3个1小时间隔的桶:
{..."aggregations": {"by_day": {"buckets": [{"key_as_string": "2015-10-01T00:00:00.000Z","key": 1443657600000,"doc_count": 1},{"key_as_string": "2015-10-01T01:00:00.000Z","key": 1443661200000,"doc_count": 1},{"key_as_string": "2015-10-01T02:00:00.000Z","key": 1443664800000,"doc_count": 1}],"interval": "1h"}}
}
如果指定了time_zone
的值为-01:00
,则午夜从 UTC 午夜前一小时开始:
GET my_index/_search?size=0
{"aggs": {"by_day": {"auto_date_histogram": {"field": "date","buckets" : 3,"time_zone": "-01:00"}}}
}
现在仍返回3个1小时间隔的桶,但第一个桶开始于2015年9月30日晚上11:00,因为这是该时段在指定时区的当地时间。
{..."aggregations": {"by_day": {"buckets": [{"key_as_string": "2015-09-30T23:00:00.000-01:00", "key": 1443657600000,"doc_count": 1},{"key_as_string": "2015-10-01T00:00:00.000-01:00","key": 1443661200000,"doc_count": 1},{"key_as_string": "2015-10-01T01:00:00.000-01:00","key": 1443664800000,"doc_count": 1}],"interval": "1h"}}
}
key_as_string
值表示指定时区中每天的午夜。
当使用跟随 DST(夏令时)变化的时区时,靠近这些变化发生时刻的桶可能与相邻桶的大小略有不同。 例如,在
CET
时区开始夏令时:2016年3月27日凌晨2点,时钟拨快1小时至当地时间凌晨3点。 如果聚合的结果是每日一个桶,则当天的时段将仅保存23小时的数据,而不是其他时段通常的24小时。 对于较短的时间间隔,例如12小时,情况也是如此。 在这里,当夏令时转换发生时,我们在3月27日早上只有一个11小时的桶。
脚本
与普通的 date_histogram
(日期直方图) 一样,文档级脚本和值级脚本都受支持。 但是,此聚合不支持min_doc_count
、extended_bounds
及 order
参数。
参数 minimum_interval
minimum_interval
允许调用者指定应该使用的最小舍入区间。 这可以提高收集过程的效率,因为聚合不会以任何低于 minimum_interval
的时间间隔进行舍入。
minimum_interval
可以接受的单位有:
- year
- month
- day
- hour
- minute
- second
POST /sales/_search?size=0
{"aggs" : {"sale_date" : {"auto_date_histogram" : {"field" : "date","buckets": 10,"minimum_interval": "minute"}}}
}
缺失的值
参数 missing
定义应该如何处理有缺失值的文档。 默认情况下,它们会被忽略,但也可以将它们视为有一个(默认)值。
POST /sales/_search?size=0
{"aggs" : {"sale_date" : {"auto_date_histogram" : {"field" : "date","buckets": 10,"missing": "2000/01/01" }}}
}
- 在
publish_date
字段中没有值的文档将与值为2000-01-01
的文档落入同一个桶中。
children 聚合
一种特殊的单桶聚合,它选择具有指定类型的子文档,如 join
字段中定义的那样。
这个聚合只有一个选项:
type
- 应该选择的子类型。
例如,假设我们有一个问答索引。答案的类型在映射中具有以下join
字段:
PUT child_example
{"mappings": {"properties": {"join": {"type": "join","relations": {"question": "answer"}}}}
}
question
文档包含 tag 字段,answer
文档包含 owner 字段。 使用 children
聚合,即使两个字段存在于两种不同类型的文档中,也可以在单个请求中将 tag 桶映射到 owner 桶。
question
文档的示例:
PUT child_example/_doc/1
{"join": {"name": "question"},"body": "<p>I have Windows 2003 server and i bought a new Windows 2008 server...","title": "Whats the best way to file transfer my site from server to a newer one?","tags": ["windows-server-2003","windows-server-2008","file-transfer"]
}
answer
文档的示例:
PUT child_example/_doc/2?routing=1
{"join": {"name": "answer","parent": "1"},"owner": {"location": "Norfolk, United Kingdom","display_name": "Sam","id": 48},"body": "<p>Unfortunately you're pretty much limited to FTP...","creation_date": "2009-05-04T13:45:37.030"
}PUT child_example/_doc/3?routing=1&refresh
{"join": {"name": "answer","parent": "1"},"owner": {"location": "Norfolk, United Kingdom","display_name": "Troll","id": 49},"body": "<p>Use Linux...","creation_date": "2009-05-05T13:45:37.030"
}
可以构建以下请求,将两者连接在一起:
POST child_example/_search?size=0
{"aggs": {"top-tags": {"terms": {"field": "tags.keyword","size": 10},"aggs": {"to-answers": {"children": {"type" : "answer" },"aggs": {"top-names": {"terms": {"field": "owner.display_name.keyword","size": 10}}}}}}}
}
type
指向名为answer
的类型/映射。
上面的例子返回前几个问题标签和每个标签前几个答案的所有者。
响应可能是:
{"took": 25,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped" : 0,"failed": 0},"hits": {"total" : {"value": 3,"relation": "eq"},"max_score": null,"hits": []},"aggregations": {"top-tags": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "file-transfer","doc_count": 1, "to-answers": {"doc_count": 2, "top-names": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "Sam","doc_count": 1},{"key": "Troll","doc_count": 1}]}}},{"key": "windows-server-2003","doc_count": 1, "to-answers": {"doc_count": 2, "top-names": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "Sam","doc_count": 1},{"key": "Troll","doc_count": 1}]}}},{"key": "windows-server-2008","doc_count": 1, "to-answers": {"doc_count": 2, "top-names": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "Sam","doc_count": 1},{"key": "Troll","doc_count": 1}]}}}]}}
}
- 标签为
file-transfer
、windows-server-2003
等的 question 文档的数量。 - 与带有标签
file-transfer
、windows-server-2003
等的 question 文档相关的 answer 文档的数量。
composite(复合聚合)
一个多桶(multi-bucket)聚合,从不同来源创建复合桶。
与其他多桶(multi-bucket)
聚合不同,composite
聚合可用于高效地对多级聚合中的所有桶进行分页。 这种聚合提供了一种方法,类似于 scroll 对文档所做的那样,对特定聚合的所有桶进行流式处理。
复合桶是从为每个文档提取/创建的值的组合中构建的,并且每个组合被认为是一个复合桶。
比如下面的文档:
{"keyword": ["foo", "bar"],"number": [23, 65, 76]
}
... 当keyword
和 number
用作聚合的值的来源时,创建以下复合桶:
{ "keyword": "foo", "number": 23 }
{ "keyword": "foo", "number": 65 }
{ "keyword": "foo", "number": 76 }
{ "keyword": "bar", "number": 23 }
{ "keyword": "bar", "number": 65 }
{ "keyword": "bar", "number": 76 }
值的来源(value source)
参数 sources
控制应该用于构建复合桶的源。 定义 sources
的顺序很重要,因为它也控制着 key 的返回顺序。
每个源的名称必须是唯一的。
值的来源有三种不同类型:
terms (词项)
terms
值来源相当于一个简单的 terms
集合。 这些值是从字段或脚本中提取的,就像 terms
聚合一样。
例如:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "product": { "terms" : { "field": "product" } } }]}}}
}
与 terms
聚合一样,也可以使用脚本来创建复合桶的值:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{"product": {"terms" : {"script" : {"source": "doc['product'].value","lang": "painless"}}}}]}}}
}
histogram (直方图)
histogram
值来源可应用于数值,以在这些值上构建固定大小的间隔。 参数 interval
定义应该如何转换数值。 例如, interval
设置为 5 会将任何数值转换为最接近 5 的间隔,值 101
会转换为 100
,因为 101 在间隔 100 和 105 之间。
例如:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "histo": { "histogram" : { "field": "price", "interval": 5 } } }]}}}
}
这些值由 numeric(数值)字段或返回数值的脚本构建而成:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{"histo": {"histogram" : {"interval": 5,"script" : {"source": "doc['price'].value","lang": "painless"}}}}]}}}
}
date histogram (日期直方图)
date_histogram
值源类似于 histogram
,只是它的时间间隔是由日期/时间表达式指定的:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "date": { "date_histogram" : { "field": "timestamp", "calendar_interval": "1d" } } }]}}}
}
上面的示例创建了一个每天的时间间隔,并将所有 timestamp
值转换为最接近的时间间隔的开始。 间隔的可用表达式有:year
、quarter
, month
、week
、day
、hour
、minute
、second
。
时间值也可以通过时间单位(time units)解析支持的缩写来指定。 请注意,不支持带小数点的时间值,但是你可以通过转换到另一个时间单位来解决这个问题(例如,可以将1.5h
指定为90m
)。
format
在内部,一个日期被表示为一个64位的数字-一个时间戳,以毫秒为单位。 这些时间戳作为桶的 key 返回。 可以使用参数format
指定的格式返回格式化的日期字符串:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{"date": {"date_histogram" : {"field": "timestamp","calendar_interval": "1d","format": "yyyy-MM-dd" }}}]}}}
}
- 支持日期格式/模式表达式
时区
日期时间以 UTC 存储在 Elasticsearch 中。 默认情况下,所有的分桶和舍入也在 UTC 中完成。time_zone
参数可用于指示分桶时应该使用不同的时区。
时区可以指定为 ISO 8601 UTC 时差(例如+01:00
或-08:00
),也可以指定为时区id(在TZ数据库中使用的标识符),比如America/Los_Angeles
。
Offset
使用offset
参数按指定的正(+
)或负(-
)偏移量持续时间来更改每个桶的起始值,例如1h
表示一个小时,1d
表示一天。 有关更多可能的持续时间选项,请参见时间单位(time units)。
例如,当使用day
作为时间间隔时,每个桶的时间区间从午夜到午夜。 将参数offset
设置为+6h
会将每个桶的时间区间更改为从早上6点到早上6点:
#添加并索引两个文档
PUT my_index/_doc/1?refresh
{"date": "2015-10-01T05:30:00Z"
}PUT my_index/_doc/2?refresh
{"date": "2015-10-01T06:30:00Z"
}#搜索
GET my_index/_search?size=0
{"aggs": {"my_buckets": {"composite" : {"sources" : [{"date": {"date_histogram" : {"field": "date","calendar_interval": "day","offset": "+6h","format": "iso8601"}}}]}}}
}
上面的请求的单个桶不是从午夜开始,而是从早上6点开始:
{..."aggregations": {"my_buckets": {"after_key": { "date": "2015-10-01T06:00:00.000Z" },"buckets": [{"key": { "date": "2015-09-30T06:00:00.000Z" },"doc_count": 1},{"key": { "date": "2015-10-01T06:00:00.000Z" },"doc_count": 1}]}}
}
在进行 time_zone
调整后,计算每个桶的起始 offset
值。
混合不同的值源
参数 sources
接受一个值源数组。可以混合不同的值源来创建复合桶。例如:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } },{ "product": { "terms": {"field": "product" } } }]}}}
}
这将从由两个值源创建的值创建复合桶,一个date_histogram
和一个terms
。 每个桶由两个值组成,聚合中定义的每个值源对应一个值。 允许任何类型的组合,并且在组合桶中保留数组中的顺序。
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "shop": { "terms": {"field": "shop" } } },{ "product": { "terms": { "field": "product" } } },{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } }]}}}
}
排序 (order)
默认情况下,复合桶按其自然顺序排序。 值按其值的升序排序。 当请求多个值源时,将对每个值源进行排序,将组合桶的第一个值与另一个组合桶的第一个值进行比较,如果它们相等,则组合桶中的下一个值将用于再次比较。 这意味着复合桶 [foo, 100]
被认为比 [foobar, 0]
小,因为 foo
被认为比 foobar
小。 通过直接在值源定义中将 order
设置为asc
(升序,默认值)或desc
(降序),可以定义每个值源的排序方向。例如:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },{ "product": { "terms": {"field": "product", "order": "asc" } } }]}}}
}
当比较 date_histogram
源中的值时,将按降序对复合桶进行排序,当比较 terms
源中的值时,将按升序对复合桶进行排序。
缺失的桶
默认情况下,没有给定来源值的文档将被忽略。 通过将 missing_bucket
设置为 true
(默认为 false
),可以将它们包含在响应中:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "product_name": { "terms" : { "field": "product", "missing_bucket": true } } }]}}}
}
在上面的示例中,对于字段 product
没有值的文档,源 product_name
将生成一个显式的 null
值。 源中指定的 order
决定了 null
应该排在第一位(升序,asc
)还是最后一位(降序,desc
)。
size
可以设置参数 size
来定义应该返回多少个复合桶。 每个组合桶都被视为一个桶,因此将大小设置为 10 将返回从值源创建的前 10 个组合桶。 响应的数组中包含每个组合桶的值,该数组包含从每个值源提取的值。
分页(pagination)
如果复合桶的数量太多(或未知)而无法在单个响应中返回,则可以将检索分成多个请求。 因为复合桶本质上是扁平的,所以请求的 size
正好是响应中返回的复合桶的数量(假设它们至少是要返回的 size
个复合桶)。 如果应该检索所有的组合桶,最好使用一个较小的 size
值(例如 100
或 1000
),然后使用参数 after
检索下一个结果。例如:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"size": 2,"sources" : [{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } },{ "product": { "terms": {"field": "product" } } }]}}}
}
... 返回:
{..."aggregations": {"my_buckets": {"after_key": {"date": 1494288000000,"product": "mad max"},"buckets": [{"key": {"date": 1494201600000,"product": "rocky"},"doc_count": 1},{"key": {"date": 1494288000000,"product": "mad max"},"doc_count": 2}]}}
}
要获得下一组桶,请重新发送相同的聚合,将参数 after
设置为响应中返回的 after_key
的值。 例如,下面的请求使用在之前的响应中提供的 after_key
的值:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"size": 2,"sources" : [{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },{ "product": { "terms": {"field": "product", "order": "asc" } } }],"after": { "date": 1494288000000, "product": "mad max" } }}}
}
- 应该将聚合限制为在提供的值的排序之后(after)的桶。
after_key
通常是响应中返回的最后一个桶的key,但这并不能保证。 总是使用返回的after_key
,而不是从桶中取出它。
提前终止 (early termination)
为了获得最佳性能,应该对索引设置索引排序(index sort),以便它匹配复合聚合中的部分或全部源顺序。 例如下面的索引排序:
PUT twitter
{"settings" : {"index" : {"sort.field" : ["username", "timestamp"], "sort.order" : ["asc", "desc"] }},"mappings": {"properties": {"username": {"type": "keyword","doc_values": true},"timestamp": {"type": "date"}}}
}
- 该索引首先按
username
排序,然后按timestamp
排序。 - …
username
字段按升序排列,timestamp
字段按降序排列。可用于优化这些复合聚合:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "user_name": { "terms" : { "field": "user_name" } } } ]}}}
}
user_name
是索引排序和顺序匹配(asc
)的前缀。
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "user_name": { "terms" : { "field": "user_name" } } }, { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } } ]}}}
}
user_name
是索引排序和顺序匹配(asc
)的前缀。timestamp
也匹配前缀,并且顺序匹配(desc
)。
为了对提前终止(early termination)进行优化,建议将请求中的track_total_hits
设置为false
。 匹配请求的总命中数可以在第一次请求时检索,在每一页上计算这个数值的成本是很高的:
GET /_search
{"size": 0,"track_total_hits": false,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "user_name": { "terms" : { "field": "user_name" } } },{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } }]}}}
}
请注意,源的顺序很重要,在下面的示例中,用 timestamp
调换 user_name
将会禁用排序优化,因为这种配置与索引排序的规范不匹配。 如果源的顺序对你的用例不重要,可以遵循这些简单的准则:
- 将基数最高的字段放在第一位。(这个与 MySQL 的优化类似)
- 确保字段的顺序与索引排序的顺序相匹配。
- 将多值字段放在最后,因为它们不能用于提前终止。
索引排序(index sort) 会降低索引编排的速度,使用你的特定用例和数据集测试索引排序以确保它符合你的要求是非常重要的。 即使你没有注意到这一点,composite
聚合也会尝试在查询匹配所有文档(match_all
查询)的情况下提前终止非排序索引。
子聚合
与任何 multi-bucket
(多桶) 聚合一样,composite
聚合可以包含子聚合。 这些子聚合可用于计算其他桶或由此父聚合创建的每个复合桶的统计数据。 例如,下面的示例计算每个复合桶的 price 字段的平均值:
GET /_search
{"size": 0,"aggs" : {"my_buckets": {"composite" : {"sources" : [{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },{ "product": { "terms": {"field": "product" } } }]},"aggregations": {"the_avg": {"avg": { "field": "price" }}}}}
}
... 返回:
{..."aggregations": {"my_buckets": {"after_key": {"date": 1494201600000,"product": "rocky"},"buckets": [{"key": {"date": 1494460800000,"product": "apocalypse now"},"doc_count": 1,"the_avg": {"value": 10.0}},{"key": {"date": 1494374400000,"product": "mad max"},"doc_count": 1,"the_avg": {"value": 27.0}},{"key": {"date": 1494288000000,"product" : "mad max"},"doc_count": 2,"the_avg": {"value": 22.5}},{"key": {"date": 1494201600000,"product": "rocky"},"doc_count": 1,"the_avg": {"value": 10.0}}]}}
}
管道聚合(pipeline aggregations)
目前复合聚合与管道聚合不兼容,在大多数情况下也没有意义。 例如,由于复合聚合的分页特性,单个逻辑分区(例如一天)可能会分布在多个页面上。 由于管道聚合纯粹是对最终桶列表的后处理,在复合页面上运行类似派生的东西可能会导致不准确的结果,因为它只考虑该页面上的“部分”结果。
将来可能会支持自包含到单个桶(如bucket_selector
)的管道聚合。
date_histogram(日期直方图聚合)
这种多桶聚合类似于普通的 histogram(直方图聚合),但它只能用于日期或日期范围值。 因为 Elasticsearch 中的日期在内部用长整形表示,所以也可以对日期使用普通histogram
聚合,但不够精确。 这两个API的主要区别在于,日期直方图可以使用日期/时间表达式来指定时间间隔。 基于时间的数据需要特殊的支持,因为基于时间的间隔并不总是固定的长度。
日历和固定间隔值
配置日期直方图聚合时,可以通过两种方式指定时间间隔:日历感知(calendar-aware)时间间隔和固定(fixed)时间间隔。
日历感知间隔知道夏令时改变特定日子的长度,月份有不同的天数,闰秒可以附加到特定的年份。
相比之下,固定间隔始终是SI(国际)单位的倍数,并且不会根据日历上下文改变。
组合的
interval
字段已弃用
[7.2] 在 7.2 版本中废弃。interval
字段已废弃过去,日历和固定时间间隔都是在一个interval
字段中配置的,这导致了语义混乱。 指定1d
会被认为是一个日历感知时间,而2d
却被解释为一个固定时间。 要获得固定时间类型的“一天”,用户需要指定下一个更小的单位(在本例中是24h
)。
这种组合行为对于用户来说通常是未知的,即使知道这种行为,也很难使用且容易混淆。
这种行为已被弃用,取而代之的是两个新的显式字段:calendar_interval
和fixed_interval
。
通过预先在日历和间隔之间进行选择,时间间隔的语义对用户来说是一目了然的,不会有歧义。 旧的interval
字段会在将来移除。
日历间隔
日历感知间隔是用参数 calendar_interval
配置的。 日历间隔只能以单位的“单数”数量指定(1d
、1M
等)。 不支持像 2d
这样的倍数,否则会引发异常。
日历间隔可用的单位有:
- 分钟 (
1m
)
所有的分钟都从 00 秒开始。一分钟是指定时区中第一分钟的 00 秒和下一分钟的 00 秒之间的时间间隔,用于补偿任何介于其间的闰秒,因此整点后的分钟数和秒数在开始和结束时是相同的。 - 小时 (
1h
)
所有的小时都从 00 分 00 秒开始。一个小时(1h)是指定时区中第一个小时的 00:00 分钟和下一个小时的 00:00 分钟之间的间隔,用于补偿任何介于其间的闰秒,以便在开始和结束时超过该小时的分钟数和秒数是相同的。 - 天 (
1d
)
所有的天都在尽可能早的时间开始,通常是 00:00:00(午夜)。一天(1d)是指定时区中一天开始和下一天开始之间的时间间隔,用于补偿任何中间的时间变化。 - 星期 (
1w
)
一周是指定时区中 一周的开始日:小时:分钟:秒(day_of_week:hour:minute:second) 与下一周的同一天和时间之间的间隔。 - 月 (
1M
)
一个月是指定时区中一个月的开始日期和时间与下一个月的同一天和时间之间的间隔,因此一个月的开始日期和时间是相同的。 - 季度 (
1q
)
一个季度(1q)是一个月的开始日期和一天中的时间与三个月后的同一天和一天中的时间之间的间隔,因此一个月的开始日期和结束日期是相同的。 - 年 (
1y
)
一年(1y)是指定时区中一个月的开始日期和时间与下一年的同一天和时间之间的间隔,因此开始和结束时的日期和时间是相同的。
日历间隔的示例
例如,下面是请求以日历时间中的一个月为桶间隔的聚合:
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"date_histogram" : {"field" : "date","calendar_interval" : "month"}}}
}
如果尝试使用日历单位的倍数,聚合就会失败,因为仅支持单个日历单位:
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"date_histogram" : {"field" : "date","calendar_interval" : "2d"}}}
}
{"error" : {"root_cause" : [...],"type" : "x_content_parse_exception","reason" : "[1:82] [date_histogram] failed to parse field [calendar_interval]","caused_by" : {"type" : "illegal_argument_exception","reason" : "The supplied interval [2d] could not be parsed as a calendar interval.","stack_trace" : "java.lang.IllegalArgumentException: The supplied interval [2d] could not be parsed as a calendar interval."}}
}
固定间隔
固定间隔是用参数 fixed_interval
配置的。
与日历感知间隔不同,固定间隔是固定数量的国际单位制(SI)单位,无论它们在日历上的位置如何,都不会偏离。 一秒钟总是由 1000 毫秒组成。 这允许以支持单位的任何倍数值指定固定间隔。
然而,这意味着固定的间隔不能表达其他单位,如“月(month)”,因为一个月的持续时间不是一个固定的量。 试图指定月或季度(quarter)等日历间隔将引发异常。
固定间隔支持的单位有:
- 毫秒 (ms)
- 秒 (s)
定义为每个 1000 毫秒 - 分钟 (m)
所有分钟都从 00 秒开始。 定义为每个 60 秒(60,000 毫秒) - 小时 (h)
所有小时都从 00 分 00 秒开始。 定义为每 60 分钟(3,600,000 毫秒) - 天 (d)
所有天都在尽可能早的时间开始,通常是 00:00:00(午夜)。 定义为 24 小时(86,400,000 毫秒)
固定间隔的示例
如果我们尝试重新创建之前的calendar_interval
的“月”,我们可以用 30 天的固定天数来近似:
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"date_histogram" : {"field" : "date","fixed_interval" : "30d"}}}
}
但是,如果我们尝试使用不支持的日历单位,如“周(w)”,就会得到一个异常:
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"date_histogram" : {"field" : "date","fixed_interval" : "2w"}}}
}
{"error" : {"root_cause" : [...],"type" : "x_content_parse_exception","reason" : "[1:82] [date_histogram] failed to parse field [fixed_interval]","caused_by" : {"type" : "illegal_argument_exception","reason" : "failed to parse setting [date_histogram.fixedInterval] with value [2w] as a time value: unit is missing or unrecognized","stack_trace" : "java.lang.IllegalArgumentException: failed to parse setting [date_histogram.fixedInterval] with value [2w] as a time value: unit is missing or unrecognized"}}
}
注意
在任何情况下,当指定的结束时间不存在时,实际结束时间是指定结束时间后最近的可用时间。
广泛发布的应用程序还必须考虑一些不确定因素,比如一些国家在12点01分开始和停止夏令时,因此每年都会有一分钟是周日,然后接下来的59分钟又回到了周六,以及一些国家决定跨越国际日期变更线。 这种情况使得不规则时区偏移看起来很容易。
与往常一样,严格的测试,尤其是围绕时间变化事件,将确保你的时间间隔规格是你想要的。
警告:为避免意外结果,所有连接的服务器和客户端都必须同步到可靠的网络时间服务。
不支持带小数点的时间值,但是可以通过转换到另一个时间单位来解决这个问题(例如,可以将
1.5h
指定为90m
)。
还可以使用时间单位解析支持的缩写来指定时间值。
key
在内部,日期被表示为一个 64 位的数字,该数字表示一个以毫秒为单位的时间戳,该时间戳自 UTC 时间 1970/1/1 午夜开始。 这些时间戳作为桶的 key
返回。 key_as_string
是一个使用参数format
规范转换为格式化的日期字符串的相同的时间戳:
如果未指定
format
,将使用字段映射中指定的第一个日期格式。
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"date_histogram" : {"field" : "date","calendar_interval" : "1M","format" : "yyyy-MM-dd" }}}
}
- 支持日期格式模式表达式
响应:
{..."aggregations": {"sales_over_time": {"buckets": [{"key_as_string": "2015-01-01","key": 1420070400000,"doc_count": 3},{"key_as_string": "2015-02-01","key": 1422748800000,"doc_count": 2},{"key_as_string": "2015-03-01","key": 1425168000000,"doc_count": 2}]}}
}
时区
日期时间以 UTC 存储在 Elasticsearch 中。 默认情况下,所有的分桶和舍入也在 UTC 中完成。 time_zone
参数可用于指示分桶时应该使用不同的时区。
时区可以指定为 ISO 8601 UTC 时差(例如+01:00
或 -08:00
),也可以指定为时区 id(在 TZ 数据库中使用的标识符),比如America/Los_Angeles
。
看下面的例子:
PUT my_index/_doc/1?refresh
{"date": "2015-10-01T00:30:00Z"
}PUT my_index/_doc/2?refresh
{"date": "2015-10-01T01:30:00Z"
}GET my_index/_search?size=0
{"aggs": {"by_day": {"date_histogram": {"field": "date","calendar_interval": "day"}}}
}
如果不指定时区,则使用 UTC。 这将导致这两个文档被放入同一天的桶中,该桶从 UTC 时间 2015 年 10 月 1 日午夜开始:
{..."aggregations": {"by_day": {"buckets": [{"key_as_string": "2015-10-01T00:00:00.000Z","key": 1443657600000,"doc_count": 2}]}}
}
如果指定了 time_zone
为 -01:00
,则该时区中的午夜比 UTC 的午夜早一个小时:
GET my_index/_search?size=0
{"aggs": {"by_day": {"date_histogram": {"field": "date","calendar_interval": "day","time_zone": "-01:00"}}}
}
现在,第一份文件落入 2015 年 9 月 30 日的桶中,而第二份文件落入 2015 年 10 月 1 日的桶中:
{..."aggregations": {"by_day": {"buckets": [{"key_as_string": "2015-09-30T00:00:00.000-01:00", "key": 1443574800000,"doc_count": 1},{"key_as_string": "2015-10-01T00:00:00.000-01:00", "key": 1443661200000,"doc_count": 1}]}}
}
key_as_string
的值表示指定时区中每天的午夜。
当使用随 DST(夏令时)变化的时区时,接近这些变化发生时刻的桶的大小可能会与你预期要用的
interval
略有不同。 例如,考虑在CET
时区开始夏令时:2016 年 3 月 27 日凌晨 2 点,时钟拨快 1 小时至当地时间凌晨 3 点。 如果使用day
作为interval
,则覆盖该天的桶将只保存 23 小时的数据,而不是其他桶通常的 24 小时。 对于较短的时间间隔也是如此,比如 12 小时,在 3 月 27 日早上 DST 转换发生时,桶里只有 11 个小时。
时差(offset)
使用 offset
参数按指定的正(+
)或负(-
)偏移量持续时间来更改每个桶的起始值,例如1h
表示一个小时,1d
表示一天。 有关更多可能的持续时间选项,请参见时间单位(time units)。
例如,当使用day
作为时间间隔时,每个桶的时间区间从午夜到午夜。 将参数 offset
设置为 +6h
会将每个桶的时间区间更改为从早上 6 点到早上 6 点:
PUT my_index/_doc/1?refresh
{"date": "2015-10-01T05:30:00Z"
}PUT my_index/_doc/2?refresh
{"date": "2015-10-01T06:30:00Z"
}GET my_index/_search?size=0
{"aggs": {"by_day": {"date_histogram": {"field": "date","calendar_interval": "day","offset": "+6h"}}}
}
上面的请求的每个桶不是从午夜开始,而是从早上 6 点开始:
{..."aggregations": {"by_day": {"buckets": [{"key_as_string": "2015-09-30T06:00:00.000Z","key": 1443592800000,"doc_count": 1},{"key_as_string": "2015-10-01T06:00:00.000Z","key": 1443679200000,"doc_count": 1}]}}
}
在进行 time_zone
调整后,计算每个桶的起始 offset
。
keyed 响应
将 keyed
标志设置为 true
,会将唯一的字符串键与每个桶相关联,并将范围作为哈希而不是数组返回:
POST /sales/_search?size=0
{"aggs" : {"sales_over_time" : {"date_histogram" : {"field" : "date","calendar_interval" : "1M","format" : "yyyy-MM-dd","keyed": true}}}
}
响应:
{..."aggregations": {"sales_over_time": {"buckets": {"2015-01-01": {"key_as_string": "2015-01-01","key": 1420070400000,"doc_count": 3},"2015-02-01": {"key_as_string": "2015-02-01","key": 1422748800000,"doc_count": 2},"2015-03-01": {"key_as_string": "2015-03-01","key": 1425168000000,"doc_count": 2}}}}
}
脚本
与普通 histogram 聚合一样,文档级脚本和值级脚本都是支持的。 你可以使用 order
设置控制返回的桶的顺序,并根据 min_doc_count
设置过滤返回的桶(默认情况下,将返回第一个与文档匹配的桶和最后一个桶之间的所有桶)。 该直方图还支持 extended_bounds
设置,该设置允许将直方图的边界扩展到数据本身之外。 更多信息请参考扩展的边界
。
缺失的值
参数 missing
定义了如何处理缺少值的文档。 默认情况下,它们被忽略,但是也可以将它们视为有一个值。
POST /sales/_search?size=0
{"aggs" : {"sale_date" : {"date_histogram" : {"field" : "date","calendar_interval": "year","missing": "2000/01/01" }}}
}
publish_date
字段没有值的文档将与2000-01-01
落入同一个桶。
排序(order)
默认情况下,返回的桶按照他们的 key
升序排列,但是你可以使用 order
设置来控制排序。 此设置支持与terms 聚合
相同的 order
功能。
使用脚本按某一天是一周中的第几天进行聚合
当你需要按某一天是一周中的第几天汇总结果时,请使用返回某一天是一周中的第几天的脚本:
POST /sales/_search?size=0
{"aggs": {"dayOfWeek": {"terms": {"script": {"lang": "painless","source": "doc['date'].value.dayOfWeekEnum.value"}}}}
}
响应:
{..."aggregations": {"dayOfWeek": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "7","doc_count": 4},{"key": "4","doc_count": 3}]}}
}
响应将包含以某一天是一周中的第几天为 key 的所有桶:1 表示星期一,2 表示星期二,...7 表示星期天。
date_range(日期范围聚合)
一个专用于日期值的范围(range)聚合。 这种聚合与普通范围(range)聚合的主要区别在于,from
和 to
的值可以用日期计算(Date Math)表达式表示,并且还可以指定返回的 from
和 to
响应字段的日期格式。 请注意,此聚合包括每个范围的 from
的值,但不包括 to
的值。左闭右开区间,即: [from, to)
示例:
POST /sales/_search?size=0
{"aggs": {"range": {"date_range": {"field": "date","format": "MM-yyyy","ranges": [{ "to": "now-10M/M" }, { "from": "now-10M/M" } ]}}}
}
- 小于当前时间减去 10 个月,向下舍入到月初。
- 等于等于当前时间加 10 个月,向下舍入到月初
在上面的示例中,我们创建了两个范围桶,第一个将“存储”10 个月之前的所有文档,第二个将“存储”10 个月之后的所有文档
响应:
{..."aggregations": {"range": {"buckets": [{"to": 1.4436576E12,"to_as_string": "10-2015","doc_count": 7,"key": "*-10-2015"},{"from": 1.4436576E12,"from_as_string": "10-2015","doc_count": 0,"key": "10-2015-*"}]}}
}
缺失的值
参数 missing
定义了如何处理缺少值的文档。 默认情况下,它们被忽略,但是也可以将它们视为有一个值。 这是通过添加一组 fieldname : value 映射来指定每个字段的默认值来实现的。
POST /sales/_search?size=0
{"aggs": {"range": {"date_range": {"field": "date","missing": "1976/11/30","ranges": [{"key": "Older","to": "2016/02/01"}, {"key": "Newer","from": "2016/02/01","to" : "now/d"}]}}}
}
- 在
date
字段中没有值的文档将被添加到"Older"桶中,就好像它们的日期值为“1976-11-30”一样。
日期格式/模式
此信息是从日期时间格式化复制的
所有 ASCII 字母都保留为格式模式字母,定义如下:
符号 | 含义 | 表现形式 | 示例 |
---|---|---|---|
G | 纪元 | text | AD; Anno Domini; A |
u | 年份 | year | 2004; 04 |
y | 纪元中的第几年 | year | 2004; 04 |
D | 一年中的第几天 | number | 189 |
M/L | (第几个)月 | number/text | 7; 07; Jul; July; J |
d | 一个月中的第几天 | number | 10 |
Q/q | (第几个)季度 | number/text | 3; 03; Q3; 3rd quarter |
Y | week-based-year | year | 1996; 96 |
w | week-of-week-based-year | number | 27 |
W | 一个月的第几周 | number | 4 |
E | 星期几 | text | Tue; Tuesday; T |
e/c | 本地化的星期几 | number/text | 2; 02; Tue; Tuesday; T |
F | 一个月的第几周 | number | 3 |
a | 上午/下午 | text | AM; PM |
h | 12小时制的钟点(1-12) | number | 12 |
K | 12小时制的小时 (0-11) | number | 0 |
k | 24小时制的钟点 (1-24) | number | 0 |
H | 一天的小时 (0-23) | number | 0 |
m | 分钟 | number | 30 |
s | 秒 | number | 55 |
S | 毫秒 | fraction | 978 |
A | milli-of-day | number | 1234 |
n | 纳秒 | number | 987654321 |
N | 一天的第几纳秒 | number | 1234000000 |
V | 时区ID | zone-id | America/Los_Angeles; Z; -08:30 |
z | 时区的名字 | zone-name | Pacific Standard Time; PST |
O | 本地化的时区偏移 | offset-O | GMT+8; GMT+08:00; UTC-08:00; |
X | Z基于0的时区偏移 | offset-X | Z; -08; -0830; -08:30; -083015; -08:30:15; |
x | 时区偏移 | offset-x | +0000; -08; -0830; -08:30; -083015; -08:30:15; |
Z | 时区偏移 | offset-Z | +0000; -0800; -08:00; |
p | 下一填充 | pad modifier | 1 |
' | 文本转义 | delimiter | '' |
' | 单引号 | literal | ' |
[ | 可选部分开始 | ||
] | 可选部分结束 | ||
# | 留作将来使用 | ||
{ | 留作将来使用 | ||
} | 留作将来使用 |
模式字母的数量决定了格式。
- Text
文本样式是由使用的模式字母的数量确定的。 少于 4 个模式字母将使用短格式,正好 4 个模式字母将使用完整的形式,正好 5 个字母将使用窄格式。 模式字母L
、c
及q
指定文本样式的独立形式。 - Number
如果模式字母的数量为 1,则使用最少的位数输出该值,且不进行填充。 否则,使用数字的数量作为输出字段的宽度,必要时用零填充。 以下模式字母对字母的数量有限制。c
和F
只能指定一个字母。d
、H
、h
、K
、k
、m
及s
最多可以指定2个字母。D
最多可以指定 3 个字母。 - Number/Text
如果模式字母的数量为3或更多时,使用上面的 Text 规则,否则使用上面的 Number 规则。 - Fraction
以秒的小数形式输出纳秒字段。 纳秒值有九个数字,因此模式字母的数量是从 1 到 9。 如果小于 9,则纳秒值被截断,只输出最高有效位。 - Year
字母的数量决定了最小字段宽度,在该宽度以下使用填充。 如果字母的数量是 2,则使用简化的两位数形式。 解析时,将使用基数 2000 进行解析,得到 2000 到 2099(含)范围内的年份。 如果字母的数量小于 4(但也不是 2),那么根据SignStyle.NORMAL
,只输出负年份的符号。 否则,如果超出填充宽度,根据SignStyle.EXCEEDS_PAD
,输出符号。 - ZoneId
输出时区 ID,例如Europe/Paris
。 如果字母的数量为 2,则输出时区 ID。 任何其他字母的数量都会抛出IllegalArgumentException
异常。 - Zone names
这将输出时区 ID 的显示名称。 如果字母数是一、二或三,则输出简称;如果字母数为 4,则输出全名;五个或更多字母抛出IllegalArgumentException
异常。 - Offset X 和 x
这将根据模式字母的数量格式化 offset。 一个字母只输出小时,如+01
,除非分钟是非零的,在这种情况下,分钟也输出,如+0130
。 两个字母输出小时和分钟,不带冒号,如+0130
。 三个字母输出小时和分钟,带冒号,如+01:30
。 四个字母输出小时、分钟和可选的秒,不带冒号,例如+013015
。 五个字母输出小时、分钟和可选的秒,带冒号,如+01:30:15
。 六个或更多的字母则抛出IllegalArgumentException
异常。 当要输出的偏移为零时,模式字母X
(大写)将输出Z
,而模式字母x
(小写)将输出+00
、+0000
或+00:00
。 - Offset O
这将根据模式字母的数量格式化本地化的 offset。 一个字母输出本地化 offset 的简短形式,它是本地化的 offset 的文本,如GMT
,包括不带前导零的小时、可选的 2 位数分钟和秒(如果非零)以及冒号,如GMT+8
。 四个字母输出完整的形式,这是本地化的 offset 的文本,如GMT
,具有两位数的小时和分钟字段,非零时的可选的第二个字段,以及冒号,例如GMT+08:00
。 任何其他字母计数都会抛出IllegalArgumentException
异常。 - Offset Z
这将根据模式字母的数量格式化 offset。 一个、两个或三个字母输出小时和分钟,不带冒号,如+0130
。 当 offset 为零时,输出将为+0000
。 四个字母输出本地化 offset 的完整形式,相当于 Offset-O 的四个字母。 如果 offset 为零,输出将是相应的本地化 offset 文本。 五个字母输出小时、分钟,如果非零,可选秒,带冒号。 如果 offset 为零,则输出Z
。 六个或更多字母抛出IllegalArgumentException
异常。 - 可选(optional)部分
可选部分标记的工作方式与调用DateTimeFormatterBuilder.optionalStart()
和DateTimeFormatterBuilder.optionalEnd()
完全一样。 - 填充修饰符
将紧随其后的模式修改为用空格填充。 填充宽度由模式字母的数量决定。 这与调用DateTimeFormatterBuilder.padNext(int)
相同。
例如,ppH
输出在左边用空格填充宽度为2的的一天中的小时。
任何无法识别的字母都是错误的。 除[
、]
、{
、}
、#
和单引号之外的任何非字母字符将被直接输出。 尽管如此,还是建议在所有想要直接输出的字符周围使用单引号,以确保将来的更改不会破坏应用程序。
日期范围聚合中的时区
通过指定 time_zone
参数,可以将日期从另一个时区转换为UTC。
时区可以指定为 ISO 8601 UTC 时差(例如 +01:00 或 -08:00),也可以指定为 TZ 数据库中的时区 id 之一。
参数 time_zone
也适用于日期数学表达式中的舍入。 例如,要在 CET 时区中舍入到一天的开始,可以执行以下操作:
POST /sales/_search?size=0
{"aggs": {"range": {"date_range": {"field": "date","time_zone": "CET","ranges": [{ "to": "2016/02/01" }, { "from": "2016/02/01", "to" : "now/d" }, { "from": "now/d" }]}}}
}
- 此日期将被转换为
2016-02-01T00:00:00.000+01:00
。 now/d
将被舍入到CET时区中一天的开始。
keyed 响应
将 keyed
标志设置为 true
会将唯一的字符串键与每个桶相关联,并将范围作为哈希而不是数组返回:
POST /sales/_search?size=0
{"aggs": {"range": {"date_range": {"field": "date","format": "MM-yyy","ranges": [{ "to": "now-10M/M" },{ "from": "now-10M/M" }],"keyed": true}}}
}
响应:
{..."aggregations": {"range": {"buckets": {"*-10-2015": {"to": 1.4436576E12,"to_as_string": "10-2015","doc_count": 7},"10-2015-*": {"from": 1.4436576E12,"from_as_string": "10-2015","doc_count": 0}}}}
}
也可以为每个范围定制一个键:
POST /sales/_search?size=0
{"aggs": {"range": {"date_range": {"field": "date","format": "MM-yyy","ranges": [{ "from": "01-2015", "to": "03-2015", "key": "quarter_01" },{ "from": "03-2015", "to": "06-2015", "key": "quarter_02" }],"keyed": true}}}
}
响应:
{..."aggregations": {"range": {"buckets": {"quarter_01": {"from": 1.4200704E12,"from_as_string": "01-2015","to": 1.425168E12,"to_as_string": "03-2015","doc_count": 5},"quarter_02": {"from": 1.425168E12,"from_as_string": "03-2015","to": 1.4331168E12,"to_as_string": "06-2015","doc_count": 2}}}}
}
diversified_sampler(多样化取样聚合)
与 sampler
聚合一样,这是一个过滤聚合,用于将任何子聚合的处理限制在得分最高的文档样本中。 diversified_sampler
聚合增加了限制共享一个公共值(如“author”)的匹配数量的能力。
任何一个优秀的市场调研人员都会告诉你,在处理数据样本时,重要的是样本要代表健康的各种观点,而不是被任何单一的声音所扭曲。 聚合也是如此,使用这些多样化的设置进行采样可以提供一种方法来消除内容中的偏见(人口过多的地理位置、时间轴上的一个大峰值或过于活跃的论坛垃圾邮件发送者)。
用例示例:
- 将分析的焦点集中在高相关性匹配上,而不是潜在的低质量匹配长尾上
- 通过确保公平呈现不同来源的内容,消除分析中的偏见
- 降低仅使用样本(例如
significant_terms
)即可产生有用结果的聚合的运行成本
选择field
或script
设置来提供用于数据去重的值,max_docs_per_value
设置控制在任何一个共享公共值的分片上收集的文档的最大数量。max_docs_per_value
的默认设置为1
。
如果选择的field
或script
为单个文档产生多个值,则聚合将引发错误(出于效率考虑,不支持使用多值字段进行去重)。
例如:
我们可能想看看哪些标签与 StackOverflow 论坛帖子上的#elasticsearch
密切相关,但忽略了一些高产用户倾向于将 #Kibana 拼写为 #Cabana 的影响。
POST /stackoverflow/_search?size=0
{"query": {"query_string": {"query": "tags:elasticsearch"}},"aggs": {"my_unbiased_sample": {"diversified_sampler": {"shard_size": 200,"field" : "author"},"aggs": {"keywords": {"significant_terms": {"field": "tags","exclude": ["elasticsearch"]}}}}}
}
响应:
{..."aggregations": {"my_unbiased_sample": {"doc_count": 151,"keywords": {"doc_count": 151,"bg_count": 650,"buckets": [{"key": "kibana","doc_count": 150,"score": 2.213,"bg_count": 200}]}}}
}
- 一共抽样了 151 份分档。
significant_terms
聚合的结果不会因为任何一个作者的怪癖而扭曲,因为我们要求样本中的任何一个作者最多发表一篇文章。
脚本化示例:
在这个场景下,我们可能希望对字段值的组合进行多样化。 我们可以使用一个 script
来产生一个 tags 字段中的多个值的哈希,以确保我们没有一个由相同的重复的 tag 组合组成的样本。
POST /stackoverflow/_search?size=0
{"query": {"query_string": {"query": "tags:kibana"}},"aggs": {"my_unbiased_sample": {"diversified_sampler": {"shard_size": 200,"max_docs_per_value" : 3,"script" : {"lang": "painless","source": "doc['tags'].hashCode()"}},"aggs": {"keywords": {"significant_terms": {"field": "tags","exclude": ["kibana"]}}}}}
}
响应:
{..."aggregations": {"my_unbiased_sample": {"doc_count": 6,"keywords": {"doc_count": 6,"bg_count": 650,"buckets": [{"key": "logstash","doc_count": 3,"score": 2.213,"bg_count": 50},{"key": "elasticsearch","doc_count": 3,"score": 1.34,"bg_count": 200}]}}}
}
shard_size
参数 shard_size
限制了在每个分片上处理的样本中收集多少个得分最高的文档。默认值为 100
。
max_docs_per_value
max_docs_per_value
是一个可选参数,它限制每次选择数值去重时允许的文档数量。默认值为 1
。
execution_hint
可选的 execution_hint
设置会影响用于值的去重的管理。 在执行去重时,每个选项最多可在内存中保存的个数为 shard_size
,但保存的值的类型可以控制如下:
- 直接保存字段值(
map
) - 保存由 Lucene 索引确定的字段序号(
global_ordinals
) - 保存字段值的哈希 - 这有可能导致哈希冲突(
bytes_hash
)
如果这个信息可以从 Lucene 索引中获得,则默认是使用global_ordinals
,否则,就恢复使用map
。bytes_hash
设置在某些情况下可能会更快,但由于可能会发生哈希冲突,因此可能会在去重逻辑中引入误报。 请注意,如果不适用,Elasticsearch 将忽略执行提示的选择,并且这些提示没有向后兼容性保证。
局限性
不能在 breadth_first 聚合下嵌套
作为一个基于质量的过滤器,diversified_sampler
聚合需要访问每个文档的相关性分数。 因此,它不能嵌套在将 collect_mode
从默认的depth_first
模式切换到 breadth_first
模式的 terms
聚合下,因为这个模式下会丢弃分数。 在这种情况下,将会抛出一个错误。
有限的去重逻辑
去重逻辑仅适用于分片级别,因此不适用于所有分片。
geo/date 字段没有专门的语法
目前,定义多样化值的语法是通过选择 field
或 script
来定义的——没有添加语法糖来表示 geo 或 date 单位,如“7d”(7天)。 此项支持可能会在以后的版本中添加,用户现在必须使用脚本来创建这些类型的值。
filter(过滤器聚合)
定义当前文档集上下文中匹配指定过滤器(filter)的所有文档的单个桶。 这通常用于将当前的聚合上下文缩小到一组特定的文档。
示例:
POST /sales/_search?size=0
{"aggs" : {"t_shirts" : {"filter" : { "term": { "type": "t-shirt" } },"aggs" : {"avg_price" : { "avg" : { "field" : "price" } }}}}
}
上面的例子计算了所有 t-shirt 类型产品的平均价格(price)。
响应:
{..."aggregations" : {"t_shirts" : {"doc_count" : 3,"avg_price" : { "value" : 128.33333333333334 }}}
}
filters(多个过滤器聚合)
定义一个多桶聚合,其中每个桶都与一个过滤器相关联。每个桶都会收集与其关联的过滤器匹配的所有文档。
示例:
PUT /logs/_bulk?refresh
{ "index" : { "_id" : 1 } }
{ "body" : "warning: page could not be rendered" }
{ "index" : { "_id" : 2 } }
{ "body" : "authentication error" }
{ "index" : { "_id" : 3 } }
{ "body" : "warning: connection timed out" }GET logs/_search
{"size": 0,"aggs" : {"messages" : {"filters" : {"filters" : {"errors" : { "match" : { "body" : "error" }},"warnings" : { "match" : { "body" : "warning" }}}}}}
}
上面的例子是对日志(logs)的消息的分析。 例子中的聚合将构建两个日志的消息的集合(桶)——一个用于所有包含错误的消息,另一个用于所有包含警告的消息。
响应:
{"took": 9,"timed_out": false,"_shards": ...,"hits": ...,"aggregations": {"messages": {"buckets": {"errors": {"doc_count": 1},"warnings": {"doc_count": 2}}}}
}
匿名过滤器
filters 字段也可以指定为一个筛选器数组,比如下面这个请求:
GET logs/_search
{"size": 0,"aggs" : {"messages" : {"filters" : {"filters" : [{ "match" : { "body" : "error" }},{ "match" : { "body" : "warning" }}]}}}
}
过滤后的桶按照请求中给定的顺序返回。这个例子的响应是:
{"took": 4,"timed_out": false,"_shards": ...,"hits": ...,"aggregations": {"messages": {"buckets": [{"doc_count": 1},{"doc_count": 2}]}}
}
其他桶(other_bucket)
可以设置 other_bucket
参数,以便向响应中添加一个桶,该桶将包含不匹配任何给定的过滤器的所有文档。 该参数的值可以设置为:
- false
不会计算other
桶 - true
计算并返回other
桶,当使用命名过滤器时返回一个命名(默认名称为_other_
)的桶,否则(使用匿名过滤器时)就是返回的桶中的最后一个
参数other_bucket_key
可用于设置other
桶的键,以取代默认的_other_
。 设置此参数将隐式地将other_bucket
参数设置为true
。
下面的代码片段显示了一个响应,其中other
桶在请求中被命名为other_messages
。
PUT logs/_doc/4?refresh
{"body": "info: user Bob logged out"
}GET logs/_search
{"size": 0,"aggs" : {"messages" : {"filters" : {"other_bucket_key": "other_messages","filters" : {"errors" : { "match" : { "body" : "error" }},"warnings" : { "match" : { "body" : "warning" }}}}}}
}
响应应该是这样的:
{"took": 3,"timed_out": false,"_shards": ...,"hits": ...,"aggregations": {"messages": {"buckets": {"errors": {"doc_count": 1},"warnings": {"doc_count": 2},"other_messages": {"doc_count": 1}}}}
}
geo_distance(地理距离聚合)
一个多桶聚合,作用于 geo_point
字段,在概念上与 range 聚合非常相似。 用户可以定义一个原点和一组距离范围桶。 该聚合计算每个文档值与原点的距离,并根据范围确定其所属的桶(如果文档与原点之间的距离在该桶的距离范围内,则该文档属于该桶)。
PUT /museums
{"mappings": {"properties": {"location": {"type": "geo_point"}}}
}POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "52.374081,4.912350", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "52.369219,4.901618", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "52.371667,4.914722", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "51.222900,4.405200", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "48.861111,2.336389", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "48.860000,2.327000", "name": "Musée d'Orsay"}POST /museums/_search?size=0
{"aggs" : {"rings_around_amsterdam" : {"geo_distance" : {"field" : "location","origin" : "52.3760, 4.894","ranges" : [{ "to" : 100000 },{ "from" : 100000, "to" : 300000 },{ "from" : 300000 }]}}}
}
响应:
{..."aggregations": {"rings_around_amsterdam" : {"buckets": [{"key": "*-100000.0","from": 0.0,"to": 100000.0,"doc_count": 3},{"key": "100000.0-300000.0","from": 100000.0,"to": 300000.0,"doc_count": 1},{"key": "300000.0-*","from": 300000.0,"doc_count": 2}]}}
}
指定字段的类型必须是geo_point
(只能在映射中显式设置)。 它还可以保存一个geo_point
字段数组,在这种情况下,所有这些都将在聚合过程中考虑在内。 原点可以接受 geo_point
类型支持的所有格式:
- 对象格式:
{ "lat" : 52.3760, "lon" : 4.894 }
- 这是最安全的格式,因为它对lat
和lon
值最明确 - 字符串格式:
"52.3760, 4.894"
- 第一个值是lat
,第二个是lon
- 数组格式:
[4.894, 52.3760]
- 基于GeoJson
标准,第一个值是lon
,第二个是lat
默认情况下,距离的单位是m
(米),但也接受:mi
(英里),in
(英尺),yd
(码),km
(千米),cm
(厘米),mm
(毫米).
POST /museums/_search?size=0
{"aggs" : {"rings" : {"geo_distance" : {"field" : "location","origin" : "52.3760, 4.894","unit" : "km", "ranges" : [{ "to" : 100 },{ "from" : 100, "to" : 300 },{ "from" : 300 }]}}}
}
- 距离将以千米为单位计算
有两种距离计算模式:arc
(默认) 和plane
。arc
计算是最精确的,plane
最快但最不精确。 当搜索环境“狭窄”,并且跨越较小的地理区域(约5公里)时,请考虑使用plane
。 对于非常大的区域的搜索(例如跨洲搜索),plane
将返回更高的误差。。 可以使用参数distance_type
设置距离计算类型:
POST /museums/_search?size=0
{"aggs" : {"rings" : {"geo_distance" : {"field" : "location","origin" : "52.3760, 4.894","unit" : "km","distance_type" : "plane","ranges" : [{ "to" : 100 },{ "from" : 100, "to" : 300 },{ "from" : 300 }]}}}
}
keyed 响应
将 keyed
标志设置为 true
会将唯一的字符串键与每个桶相关联,并将范围作为哈希而不是数组返回:
POST /museums/_search?size=0
{"aggs" : {"rings_around_amsterdam" : {"geo_distance" : {"field" : "location","origin" : "52.3760, 4.894","ranges" : [{ "to" : 100000 },{ "from" : 100000, "to" : 300000 },{ "from" : 300000 }],"keyed": true}}}
}
响应:
{..."aggregations": {"rings_around_amsterdam" : {"buckets": {"*-100000.0": {"from": 0.0,"to": 100000.0,"doc_count": 3},"100000.0-300000.0": {"from": 100000.0,"to": 300000.0,"doc_count": 1},"300000.0-*": {"from": 300000.0,"doc_count": 2}}}}
}
也可以为每个范围定制一个键:
POST /museums/_search?size=0
{"aggs" : {"rings_around_amsterdam" : {"geo_distance" : {"field" : "location","origin" : "52.3760, 4.894","ranges" : [{ "to" : 100000, "key": "first_ring" },{ "from" : 100000, "to" : 300000, "key": "second_ring" },{ "from" : 300000, "key": "third_ring" }],"keyed": true}}}
}
响应:
{..."aggregations": {"rings_around_amsterdam" : {"buckets": {"first_ring": {"from": 0.0,"to": 100000.0,"doc_count": 3},"second_ring": {"from": 100000.0,"to": 300000.0,"doc_count": 1},"third_ring": {"from": 300000.0,"doc_count": 2}}}}
}
geohash_grid(geohash网格聚合)
一个多桶聚合,作用于 geo_point
字段,并将坐标点分组到代表网格单元格的桶中。 生成的网格可以是稀疏的,只包含具有匹配数据的单元格。 每个单元格都使用用户可定义精度的 geohash 进行标注。
- 高精度 geohash 的字符串长度比较长,表示仅覆盖很小区域的单元格。
- 低精度 geohash 的字符串长度比较短,表示每个覆盖较大区域的单元格。
此聚合中使用的 geohash 的精度可在1和12之间选择。
长度为 12 的最高精度的 geohash 生成的单元格覆盖的土地面积不到一平方米,因此高精度请求在内存和结果大小方面的成本非常高。 请看下面的示例,了解如何在请求高精度的详细信息之前,先将聚合过滤到较小的地理区域。
指定字段的类型必须是geo_point
(只能在映射中显式设置),它还可以保存一个geo_point
字段数组,在这种情况下,所有这些都将在聚合过程中考虑在内。
简单的低精度的请求
PUT /museums
{"mappings": {"properties": {"location": {"type": "geo_point"}}}
}POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "52.374081,4.912350", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "52.369219,4.901618", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "52.371667,4.914722", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "51.222900,4.405200", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "48.861111,2.336389", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "48.860000,2.327000", "name": "Musée d'Orsay"}POST /museums/_search?size=0
{"aggregations" : {"large-grid" : {"geohash_grid" : {"field" : "location","precision" : 3}}}
}
响应:
{..."aggregations": {"large-grid": {"buckets": [{"key": "u17","doc_count": 3},{"key": "u09","doc_count": 2},{"key": "u15","doc_count": 1}]}}
}
高精度的请求
当请求详细的桶(通常用于显示“放大”的地图)时,应应用类似 geo_bounding_box 的过滤器来缩小主题区域,否则可能会创建并返回数百万个桶。
POST /museums/_search?size=0
{"aggregations" : {"zoomed-in" : {"filter" : {"geo_bounding_box" : {"location" : {"top_left" : "52.4, 4.9","bottom_right" : "52.3, 5.0"}}},"aggregations":{"zoom1":{"geohash_grid" : {"field": "location","precision": 8}}}}}
}
geohash_grid
聚合返回的 geohash 也可用于放大。 要放大上例中返回的第一个geohash u17
,应将它指定为top_left
和 bottom_right
:
POST /museums/_search?size=0
{"aggregations" : {"zoomed-in" : {"filter" : {"geo_bounding_box" : {"location" : {"top_left" : "u17","bottom_right" : "u17"}}},"aggregations":{"zoom1":{"geohash_grid" : {"field": "location","precision": 8}}}}}
}
{..."aggregations" : {"zoomed-in" : {"doc_count" : 3,"zoom1" : {"buckets" : [{"key" : "u173zy3j","doc_count" : 1},{"key" : "u173zvfz","doc_count" : 1},{"key" : "u173zt90","doc_count" : 1}]}}}
}
对于不支持 geohash 的系统上的“放大”操作,应该使用一个可用的 geohash 库将桶的键转换为边界框。 例如,对于 javascript,可以使用 node-geohash 库:
var geohash = require('ngeohash');// bbox will contain [ 52.03125, 4.21875, 53.4375, 5.625 ]
// [ minlat, minlon, maxlat, maxlon]
var bbox = geohash.decode_bbox('u17');
带有附加边界框过滤的请求
geohash_grid
聚合支持一个可选的参数-bounds
,该参数将所考虑的坐标点限制在所提供的边界内。 参数 bounds
接受 地理边界框(geo_bounding_box)查询中的所有有效的格式的边界的边界框。 此边界框可与在聚合前过滤坐标点的附加geo_bounding_box
查询一起使用,也可单独使用。 它是一个独立的边界框,可以与聚合上下文中定义的任何其他geo_bounding_box
查询相交、相等或不相交。
POST /museums/_search?size=0
{"aggregations" : {"tiles-in-bounds" : {"geohash_grid" : {"field" : "location","precision" : 8,"bounds": {"top_left" : "53.4375, 4.21875","bottom_right" : "52.03125, 5.625"}}}}
}
{..."aggregations" : {"tiles-in-bounds" : {"buckets" : [{"key" : "u173zy3j","doc_count" : 1},{"key" : "u173zvfz","doc_count" : 1},{"key" : "u173zt90","doc_count" : 1}]}}
}
赤道上的单元格的空间大小
下表显示了 geohash 的各种字符串长度所覆盖的单元格的空间纬度的大小。 单元格的尺寸随纬度变化,因此该表适用于赤道上的最坏情况。
geohash值长度 | 区域(宽 x 高) |
---|---|
1 | 5,009.4km x 4,992.6km |
2 | 1,252.3km x 624.1km |
3 | 156.5km x 156km |
4 | 39.1km x 19.5km |
5 | 4.9km x 4.9km |
6 | 1.2km x 609.4m |
7 | 152.9m x 152.4m |
8 | 38.2m x 19m |
9 | 4.8m x 4.8m |
10 | 1.2m x 59.5cm |
11 | 14.9cm x 14.9cm |
12 | 3.7cm x 1.9cm |
选项
field | 必要的。使用 GeoPoints 索引的字段的名称。 |
---|---|
precision | 可选。用于在结果中定义单元格/桶的 geohash 的字符串长度。默认值为 5。 精度可以用上面提到的整数精度等级来定义。 超出[1,12]范围的值将被拒绝。 或者,可以从“1km”、“10m”这样的距离度量来近似计算精度等级。 计算精度的等级,使得单元格不会超过所需精度的指定大小(对角线)。 当这会导致精度等级高于支持的12级时(例如距离小于5.6厘米),该值将被拒绝。 |
bounds | 可选。用于过滤桶中的坐标点的边界框。 |
size | 可选。要返回的 geohash 桶的最大数量(默认为10,000)。 当结果被裁剪(trim)时,基于桶包含的文档数量来区分桶的优先级。 |
shard_size | 可选。为了对最终结果中返回的前几个单元格进行更精确的计数,该聚合默认从每个分片返回 max(10,(size x number-of-shards)) 个桶。 如果不希望采用这种启发式方法,则可以使用此参数覆盖每个分片所考虑的数字。 |