本地英文版地址: ../en/search-aggregations-bucket-terms-aggregation.html
一种基于多桶值源的聚合,其中桶是用一个个的唯一值动态构建的。
示例:
|
响应:
{ ... "aggregations" : { "genres" : { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets" : [ { "key" : "electronic", "doc_count" : 6 }, { "key" : "rock", "doc_count" : 3 }, { "key" : "jazz", "doc_count" : 2 } ] } } }
默认情况下,terms
聚合将返回按doc_count
排序的前10个词的桶。
可以通过设置参数size
来改变这种默认行为。
参数size
可以用来定义应该从整个词项列表中返回多少个词项桶。
默认情况下,协调搜索过程的节点(被称为协调节点)将请求每个分片提供其自己的前size
个词项桶,并且一旦所有分片做出响应,它将把结果缩小到最终列表,然后将该列表返回给客户端。
这意味着,如果唯一词的数量大于size
,则返回的列表会稍有偏差且不准确(可能是词项计数稍有偏差,甚至可能是本应该在前 size 个桶中的项没有返回)。
如果你想要检索嵌套terms
聚合中的所有词项或所有词项组合,应该使用composite聚合,它允许对所有可能的词项进行分页,而不是将size设置为大于terms
聚合中字段的基数。
terms
聚合旨在返回top
(前几个)词项,不允许分页。
请求的size
越高,结果就越准确,但是计算最终结果的成本也就越高(这是因为在分片级别上管理的优先级队列越大,以及节点和客户端之间传输的数据越多)。
参数shard_size
可用于最小化因请求的size
较大而带来的额外工作。
一旦定义了,它将决定协调节点将从每个分片请求多少个词项。
一旦所有的分片都作出响应,协调节点就会将它们缩小到基于参数size
的最终结果——通过这种方式,可以提高返回项的准确性,并避免将一个大的桶列表流回客户端的开销。
shard_size
不能小于size
(因为没有太大意义)。当它存在时,Elasticsearch将覆盖它并将其重置为与size
相等。
shard_size
的默认值为 (size * 1.5 + 10)
。
在 terms 聚合中可以显示两个误差值。 第一个给出了聚合的整体值,该值表示没有进入最终词项列表的词项的最大潜在文档数。 它被计算为从每个分片返回的最后一个词项的文档计数的总和。
通过将show_term_doc_count_error
参数设置为true,可以启用第二个误差值:
GET /_search { "aggs" : { "products" : { "terms" : { "field" : "product", "size" : 5, "show_term_doc_count_error": true } } } }
这显示了聚合返回的每个词项的误差值,它代表了文档计数中最差情况的误差,在决定shard_size
参数的值时非常有用。
这是通过对没有返回词项的所有分片返回的最后一个词项的文档计数求和来计算的。
只有当词项按文档计数降序排列时,才能以这种方式计算这些误差。 当聚合按词项值本身排序(升序或降序)时,在文档计数中没有误差,因为如果一个分片没有返回出现在另一个分片的结果中的特定词项,则它的索引中一定没有该词项。 当聚合按子聚合或按文档计数升序排序时,无法确定文档计数中的误差,并给定一个值 -1 来表示这一点。
可以通过设置参数order
来指定桶的顺序。
默认情况下,桶按照doc_count
降序排列。
有可能改变这种行为,如下所示:
将桶按文档的_count
升序排序:
GET /_search { "aggs" : { "genres" : { "terms" : { "field" : "genre", "order" : { "_count" : "asc" } } } } }
将桶按按词项的字母顺序的升序排序:
GET /_search { "aggs" : { "genres" : { "terms" : { "field" : "genre", "order" : { "_key" : "asc" } } } } }
在6.0.0版本中废弃
使用_key
代替_term
,将桶按词项排序
按单值度量子聚合对桶进行排序(由聚合名称标识):
GET /_search { "aggs" : { "genres" : { "terms" : { "field" : "genre", "order" : { "max_play_count" : "desc" } }, "aggs" : { "max_play_count" : { "max" : { "field" : "play_count" } } } } } }
按多值度量子聚集对桶进行排序(由聚合名称标识):
GET /_search { "aggs" : { "genres" : { "terms" : { "field" : "genre", "order" : { "playback_stats.max" : "desc" } }, "aggs" : { "playback_stats" : { "stats" : { "field" : "play_count" } } } } } }
管道(pipeline)聚合不能用于排序
管道(pipeline)聚合在所有其他聚合完成后的压缩阶段运行。 因此,它们不能用于排序。
还可以根据层次结构中“更深”的聚合对桶进行排序。
只要聚合路径是单桶类型,就支持这一点,其中路径中的最后一个聚合可以是单桶聚合,也可以是度量聚合。
如果是单桶类型,则顺序将由桶中的文档数(即doc_count
)来定义,如果是度量类型,则应用与上述相同的规则(如果是多值度量聚合,则路径必须指示要排序的度量名称,如果是单值度量聚合,则排序将应用于该值)。
路径必须按以下形式定义:
AGG_SEPARATOR = '>' ; METRIC_SEPARATOR = '.' ; AGG_NAME = <the name of the aggregation> ; METRIC = <the name of the metric (in case of multi-value metrics aggregation)> ; PATH = <AGG_NAME> [ <AGG_SEPARATOR>, <AGG_NAME> ]* [ <METRIC_SEPARATOR>, <METRIC> ] ;
GET /_search { "aggs" : { "countries" : { "terms" : { "field" : "artist.country", "order" : { "rock>playback_stats.avg" : "desc" } }, "aggs" : { "rock" : { "filter" : { "term" : { "genre" : "rock" }}, "aggs" : { "playback_stats" : { "stats" : { "field" : "play_count" }} } } } } } }
上面的查询将根据摇滚歌曲的平均播放次数对 艺术家的国家(artist.country) 进行分类。
通过提供一组排序标准,可以使用多个标准来对桶进行排序,如下所示:
GET /_search { "aggs" : { "countries" : { "terms" : { "field" : "artist.country", "order" : [ { "rock>playback_stats.avg" : "desc" }, { "_count" : "desc" } ] }, "aggs" : { "rock" : { "filter" : { "term" : { "genre" : "rock" }}, "aggs" : { "playback_stats" : { "stats" : { "field" : "play_count" }} } } } } } }
上面的查询将根据摇滚歌曲的 平均播放次数(playback_stats.avg),然后按照doc_count
降序排列 艺术家的国家(artist.country) 类别。
如果两个桶在所有排序标准中共享相同的值,则将再按桶的词项值升序排序,以防止桶的不确定性排序。
使用min_doc_count
选项可以只返回匹配超过配置的命中数的词项:
GET /_search { "aggs" : { "tags" : { "terms" : { "field" : "tags", "min_doc_count": 10 } } } }
上述聚集将仅返回有10次或更多次命中出现的 tags。默认值为1
。
词项在分片级别上被收集和排序,并在第二步中与从其他分片收集的词项合并。
然而,分片没有关于可用的全局词项计数的信息。
是否将一个词项添加到候选列表的决定只取决于使用局部分片频率在分片上计算的排序。
min_doc_count
标准仅在合并所有分片的本地词项统计之后应用。
在某种程度上,在没有非常确定该词项是否将实际达到所需的min_doc_count
的情况下,就做出了将该词项添加为候选项的决定。
如果低频的词项填充了候选列表,这可能导致许多(全局)高频的词项在最终结果中丢失。
为了避免这种情况,可以增加shard_size
参数,以允许分片上有更多的候选项。
但是,这会增加内存消耗和网络流量。
参数shard_min_doc_count
参数shard_min_doc_count
规定了一个分片相对于min_doc_count
是否应该被实际添加到候选列表中的确定性。
只有当词项在集合中的本地分片频率高于shard_min_doc_count
时,才会考虑这些词项。
如果你的字典包含许多低频的单词,而你对这些单词不感兴趣(例如拼写错误),那么你可以设置shard_min_doc_count
参数来过滤掉分级别上的候选词,即使在合并本地频率之后,这些候选词也肯定不会达到所需的min_doc_count
。
默认情况下,shard_min_doc_count
设置为0
,除非你显式设置它,否则它不起作用。
设置min_doc_count
=0
也还是会返回不匹配任何匹配项的桶。
但是,某些返回的文档计数为零的词项可能只属于已删除的文档或其他类型的文档,因此不能保证match_all
查询会为这些词项找到正的文档计数。
当不对doc_count
进行降序排序时,min_doc_count
的高值可能会返回小于size
的桶数,因为没有从分片中收集足够的数据。
将shard_min_doc_count
设置得太高会导致词项在分片级别被过滤掉。
该值应设置为远低于min_doc_count/#shards
。
使用脚本生成一个词:
GET /_search { "aggs" : { "genres" : { "terms" : { "script" : { "source": "doc['genre'].value", "lang": "painless" } } } } }
这将把script
参数解释为使用默认脚本语言且没有脚本参数的 内联(inline)
脚本。要使用存储的脚本,请使用以下语法:
GET /_search { "aggs" : { "genres" : { "terms" : { "script" : { "id": "my_script", "params": { "field": "genre" } } } } } }
GET /_search { "aggs" : { "genres" : { "terms" : { "field" : "genre", "script" : { "source" : "'Genre: ' +_value", "lang" : "painless" } } } } }
可以过滤将用于创建桶的值。
这可以使用基于正则表达式字符串或精确值数组的include
和exclude
参数来完成。
此功能反映了 terms 聚合文档中描述的功能。
此外,include
子句可以使用partition
表达式进行过滤。
GET /_search { "aggs" : { "tags" : { "terms" : { "field" : "tags", "include" : ".*sport.*", "exclude" : "water_.*" } } } }
在上面的例子中,将为所有包含单词sport
的 tags 创建桶,除了那些以water_
开头的标签(因此值为water_sports
的 tag 不会被聚合)。
include
正则表达式将决定“允许”聚合哪些值,而exclude
将决定不应该聚合的值。
当二者都被定义时,exclude
优先,这意味着首先计算include
,然后才计算exclude
。
语法同 正则查询。
对于基于精确值的匹配,include
和exclude
参数可以简单地接受一个字符串数组,这些字符串表示在索引中找到的词项:
GET /_search { "aggs" : { "JapaneseCars" : { "terms" : { "field" : "make", "include" : ["mazda", "honda"] } }, "ActiveCarManufacturers" : { "terms" : { "field" : "make", "exclude" : ["rover", "jensen"] } } } }
有时在一个请求/响应对中需要处理非常多独特的词,因此将分析分成多个请求会很有用。 这可以通过在查询时将字段值分组到多个 partition(分区) 中,并在每个请求中只处理一个 partition(分区) 来实现。 看一下下面这个请求,它寻找最近没有任何访问记录的帐户:
GET /_search { "size": 0, "aggs": { "expired_sessions": { "terms": { "field": "account_id", "include": { "partition": 0, "num_partitions": 20 }, "size": 10000, "order": { "last_access": "asc" } }, "aggs": { "last_access": { "max": { "field": "access_date" } } } } } }
此请求是为了查找客户帐户子集记录的最后访问日期,因为我们可能希望终止一些很久没有出现的客户帐户。
num_partitions
设置要求将唯一的 account_ids 平均组织成20个分区(0到19)。
并且该请求中的partition
设置对仅考虑落入分区0的account_ids进行过滤。
后续请求应该请求分区1、分区2等,以完成堆过期帐户的分析。
注意,返回结果数量的size
设置需要和num_partitions
一起调整。
对于这个特定的帐户到期的例子,平衡size
和num_partitions
值的过程如下:
-
使用
cardinality
聚合来估计唯一 account_id 值的总数 -
为
num_partitions
选择一个值,将数字从 1) 分成更易于管理的块 -
为我们希望从每个分区得到的响应数量选择一个
size
值 - 运行一个请求做测试
如果我们试图在一个请求中做太多事情而遇到断路器错误,则必须增加num_partitions
。
如果请求成功,但按日期排序的测试响应中的最后一个帐户ID仍然是我们可能想要过期的帐户,那么我们可能会错过感兴趣的帐户,而且我们可能将这个数字设置得太低了。
以下必须二选一:
-
增加参数
size
的值以让每个分区返回更多结果(可能会占用大量内存),或者 -
增加
num_partitions
以减少每个请求的帐户数量(可能会增加总处理时间,因为我们需要发出更多的请求)
最终,这是在管理处理单个请求所需的Elasticsearch资源和客户端应用程序为完成任务必须发出的请求量之间的一个平衡。
terms
聚合不支持从同一文档中的多个字段收集词项。
原因是terms
聚合本身并不收集字符串词项值,而是使用全局序号(global ordinals)来生成字段中所有唯一值的列表。
全局序号(global ordinals)可以带来重要的性能提升,这在多个字段中是不可能实现的。
有两种方法可用于跨多个字段执行terms
聚合:
推迟子聚合的计算
对于具有许多唯一词项和少量必需结果的字段,将子聚合的计算延迟到顶层父级聚合被删除后进行可能会更有效。 通常,聚合树的所有分支都在一次深度优先的遍历中展开,然后才进行任何修剪。 在某些情况下,这可能是非常浪费的,并且会达到内存限制。 一个此类问题场景的例子是在电影数据库中查询10个最受欢迎的演员和他们的5个最常见的联合主演:
GET /_search { "aggs" : { "actors" : { "terms" : { "field" : "actors", "size" : 10 }, "aggs" : { "costars" : { "terms" : { "field" : "actors", "size" : 5 } } } } } }
尽管参与者的数量可能相对较少,我们只需要50个结果桶,但是在计算过程中会出现桶的组合爆炸——一个参与者可以产生 n² 个桶(其中n是参与者的数量)。
明智的选择是首先确定10个最受欢迎的演员,然后才检查这10个演员的最佳联合主演。
这种替代策略就是我们所说的广度优先(breadth_first)
收集模式,与 深度优先(depth_first)
模式相对。
广度优先(breadth_first)
是基数大于请求大小的字段或基数未知的字段(例如数值字段或脚本)的默认模式。
可以覆盖默认启发式规则,并在请求中直接指定收集模式:
GET /_search { "aggs" : { "actors" : { "terms" : { "field" : "actors", "size" : 10, "collect_mode" : "breadth_first" }, "aggs" : { "costars" : { "terms" : { "field" : "actors", "size" : 5 } } } } } }
当使用广度优先(breadth_first)
模式时,落入最主要的桶中的文档集被缓存以供后续重放,因此这样做存在与匹配文档的数量成线性关系的内存开销。
请注意,在使用breadth_first
设置时,参数order
仍可用于引用子聚合中的数据——父聚合知道需要在调用任何其他子聚合之前首先调用该子聚合。
诸如top_hits
之类的嵌套聚合,需要访问使用breadth_first
收集模式的聚合下的得分信息,需要在第二次传递时重放查询,但只针对属于前几个桶的文档。
可以通过不同的机制来执行 terms 聚合:
-
通过直接使用字段值来聚合每个桶的数据 (
map
) -
通过使用字段的全局序号并为每个全局序号分配一个桶 (
global_ordinals
)
Elasticsearch试图使用合理的默认值,所以这通常不需要配置。
global_ordinals
是keyword
类型字段的默认选项,它使用全局序号来动态分配桶,因此内存使用量与聚合范围内的文档值的数量成线性关系。
只有当很少文档与查询匹配时,才应该考虑map
。默认情况下,map
仅在脚本上运行聚合时使用,因为它们没有序号。
请注意,如果这个执行提示不适用,并且对这些提示没有向后兼容性保证,Elasticsearch将忽略它。
参数missing
定义应该如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为有一个值。