ES中的聚合被分为两大类:Metric度量和bucket桶。metric很像SQL中的avg、max、min等方法,而bucket就有点类似group by。

1. 单值聚合

  • 求和:

    1
    2
    3
    "aggs" : {
    "intraday_return" : { "sum" : { "field" : "change" } }
    }
  • 求最大值、最小值和平均值

1
2
3
4
5
{
"aggs" : {
"min_price" : { "min" : { "field" : "price" } }
}
}
1
2
3
4
5
{
"aggs" : {
"max_price" : { "max" : { "field" : "price" } }
}
}
1
2
3
4
5
{
"aggs" : {
"avg_grade" : { "avg" : { "field" : "grade" } }
}
}
  • 求唯一值,即不重复的字段有多少
1
2
3
4
5
6
7
8
9
{
"aggs" : {
"author_count" : {
"cardinality" : {
"field" : "author"
}
}
}
}

2. 多值聚合

  • 求百分比
1
2
3
4
5
6
7
8
9
10
{
"aggs" : {
"load_time_outlier" : {
"percentile_ranks" : {
"field" : "load_time",
"values" : [15, 30]
}
}
}
}
  • stats统计
1
2
3
4
5
{
"aggs" : {
"grades_stats" : { "stats" : { "field" : "grade" } }
}
}
  • 扩展统计
1
2
3
4
5
{
"aggs" : {
"grades_stats" : { "extended_stats" : { "field" : "grade" } }
}
}

3. terms聚合

terms聚合,它是按照某个字段中的值来分类:比如性别有男、女,就会创建两个桶,分别存放男女的信息。默认会搜集doc_count的信息,即记录有多少男生,有多少女生,然后返回给客户端,这样就完成了一个terms得统计

Bucket可以理解为一个桶,他会遍历文档中的内容,凡是符合要求的就放入按照要求创建的桶中。

  • terms聚合
1
2
3
4
5
6
7
{
"aggs" : {
"genders" : {
"terms" : { "field" : "gender" }
}
}
}
  • order排序
1
2
3
4
5
6
7
8
9
10
{
"aggs" : {
"genders" : {
"terms" : {
"field" : "gender",
"order" : { "_count" : "asc" }
}
}
}
}

也可以通过order指定一个单值的metric聚合,来排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"aggs" : {
"genders" : {
"terms" : {
"field" : "gender",
"order" : { "avg_height" : "desc" }
},
"aggs" : {
"avg_height" : { "avg" : { "field" : "height" } }
}
}
}
}
  • min_doc_count与shard_min_doc_count

聚合的字段可能存在一些频率很低的词条,如果这些词条数目比例很大,那么就会造成很多不必要的计算。
因此可以通过设置min_doc_count和shard_min_doc_count来规定最小的文档数目,只有满足这个参数要求的个数的词条才会被记录返回。

  • filter
1
2
3
4
5
6
7
8
9
10
11
{
"aggs" : {
"tags" : {
"terms" : {
"field" : "tags",
"include" : ".*sport.*",
"exclude" : "water_.*"
}
}
}
}

4. Histogram 直方图聚合

Elasticsearch支持直方图聚合,它在数字字段自动创建桶,并会扫描全部文档,把文档放入相应的桶中。这个数字字段既可以是文档中的某个字段,也可以通过脚本创建得出的。举个例子,有一个price字段,这个字段描述了商品的价格,现在想每隔5就创建一个桶,统计每隔区间都有多少个文档(商品)。如果有一个商品的价格为32,那么它会被放入30的桶中。

  • min_doc_count过滤

如果不想要显示count为0的桶,可以通过min_doc_count来设置。

1
2
3
4
5
6
7
8
9
10
11
{
"aggs" : {
"prices" : {
"histogram" : {
"field" : "price",
"interval" : 50,
"min_doc_count" : 1
}
}
}
}
  • extend_bounds

指定最小值和最大值边界.默认情况下,ES中的histogram聚合起始都是自动的,比如price字段,如果没有商品的价钱在0-5之间,0这个桶就不会显示。如果最便宜的商品是11,那么第一个桶就是10.
可以通过设置extend_bounds强制规定最小值和最大值,但是要求必须min_doc_count不能大于0,不然即便是规定了边界,也不会返回。

  • order排序
1
2
3
4
5
6
7
8
9
10
11
{
"aggs" : {
"prices" : {
"histogram" : {
"field" : "price",
"interval" : 50,
"order" : { "_key" : "desc" }
}
}
}
}

5. Date Histogram聚合

Date histogram的用法与histogram差不多,只不过区间上支持了日期的表达式。interval字段支持多种关键字:year, quarter, month, week, day, hour, minute, second

1
2
3
4
5
6
7
8
9
10
11
{
"aggs":{
"articles_over_time":{
"date_histogram":{
"field":"date",
"interval":"1M",
"format":"yyyy-MM-dd"
}
}
}
}
  • time_zone时区
1
2
3
4
5
6
7
8
9
10
11
{
"aggs":{
"by_day":{
"date_histogram":{
"field":"date",
"interval":"day",
"time_zone":"+08:00"
}
}
}
}
  • offset 使用偏移值,改变时间区间
1
2
3
4
5
6
7
8
9
10
{"aggs":{
"by_day":{
"date_histogram":{
"field":"date",
"interval":"day",
"offset":"+6h"
}
}
}
}

6. Range区间聚合

  • 聚合
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"aggs":{
"grade_ranges":{
"range":{
"field":"grade",
"ranges":[
{"to":60},
{"from":60,"to":80},
{"from":80}]
}
}
}
}
  • 聚合嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"aggs":{
"price_ranges":{
"range":{
"field":"price",
"ranges":[
{"to":50},
{"from":50,"to":100},
{"from":100}
]},
"aggs":{
"price_stats":{
"stats":{
"field":"price"
}
}
}
}
}
}

7. DateRange日期范围聚合

相比于range聚合,date range就是范围可以由时间来指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"aggs":{
"range":{
"date_range":{
"field":"date",
"format":"MM-yyy",
"ranges":[
{"to":"now-10M/M"},
{"from":"now-10M/M"}
]
}
}
}
}

8. 查询的种类

  • Elasticsearch中的DSL主要由两部分组成:

Leaf query Cluase 叶查询子句

这种查询可以单独使用,针对某一特定的字段查询特定的值,比如match、term、range等

Compound query Cluase 复合查询子句

这种查询配合其他的叶查询或者复合查询,用于在逻辑上,组成更为复杂的查询,比如bool

  • Query与Filter

查询在Query查询上下文和Filter过滤器上下文中,执行的操作是不一样的:

查询上下文:

在查询上下文中,查询会回答这个问题——“这个文档匹不匹配这个查询,它的相关度高么?”

如何验证匹配很好理解,如何计算相关度呢?之前说过,ES中索引的数据都会存储一个_score分值,分值越高就代表越匹配。另外关于某个搜索的分值计算还是很复杂的,因此也需要一定的时间。

查询上下文 是在 使用query进行查询时的执行环境,比如使用search的时候。

过滤器上下文:

在过滤器上下文中,查询会回答这个问题——“这个文档匹不匹配?”

答案很简单,是或者不是。它不会去计算任何分值,也不会关心返回的排序问题,因此效率会高一点。

过滤上下文 是在使用filter参数时候的执行环境,比如在bool查询中使用Must_not或者filter

8. 布尔查询Bool Query

query的时候,会先比较查询条件,然后计算分值,最后返回文档结果;

而filter则是先判断是否满足查询条件,如果不满足,会缓存查询过程(记录该文档不满足结果);满足的话,就直接缓存结果。

综上所述,filter快在两个方面:

1 对结果进行缓存

2 避免计算分值

bool查询也是采用more_matches_is_better的机制,因此满足must和should子句的文档将会合并起来计算分值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"bool" : {
"must" : {
"term" : { "user" : "kimchy" }
},
"filter": {
"term" : { "tag" : "tech" }
},
"must_not" : {
"range" : {
"age" : { "from" : 10, "to" : 20 }
}
},
"should" : [
{
"term" : { "tag" : "wow" }
},
{
"term" : { "tag" : "elasticsearch" }
}
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}

  • 使用named query给子句添加标记

如果想知道到底是bool里面哪个条件匹配,可以使用named query查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"bool" : {
"should" : [
{"match" : { "name.first" : {"query" : "shay", "_name" : "first"} }},
{"match" : { "name.last" : {"query" : "banon", "_name" : "last"} }}
],
"filter" : {
"terms" : {
"name.last" : ["banon", "kimchy"],
"_name" : "test"
}
}
}
}