本地英文版地址: ../en/search-aggregations-bucket-composite-aggregation.html
一个多桶(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 }
参数 sources
控制应该用于构建复合桶的源。
定义 sources
的顺序很重要,因为它也控制着 key 的返回顺序。
每个源的名称必须是唯一的。
值的来源有三种不同类型:
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
值来源可应用于数值,以在这些值上构建固定大小的间隔。
参数 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
值源类似于 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" } } } ] } } } }
默认情况下,复合桶按其自然顺序排序。
值按其值的升序排序。
当请求多个值源时,将对每个值源进行排序,将组合桶的第一个值与另一个组合桶的第一个值进行比较,如果它们相等,则组合桶中的下一个值将用于再次比较。
这意味着复合桶 [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
来定义应该返回多少个复合桶。
每个组合桶都被视为一个桶,因此将大小设置为 10 将返回从值源创建的前 10 个组合桶。
响应的数组中包含每个组合桶的值,该数组包含从每个值源提取的值。
如果复合桶的数量太多(或未知)而无法在单个响应中返回,则可以将检索分成多个请求。
因为复合桶本质上是扁平的,所以请求的 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_key
通常是响应中返回的最后一个桶的key,但这并不能保证。
总是使用返回的 after_key
,而不是从桶中取出它。
为了获得最佳性能,应该对索引设置索引排序(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" } } } }
GET /_search { "size": 0, "aggs" : { "my_buckets": { "composite" : { "sources" : [ { "user_name": { "terms" : { "field": "user_name" } } } ] } } } }
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" } } } ] } } } }
为了对提前终止(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 } } ] } } }