触类旁通Elasticsearch:聚合

目录

一、聚合的结构

二、度量集合

三、桶型聚合

四、嵌套聚合

1. 多桶聚合

2. 单桶聚合


《Elasticsearch In Action》学习笔记。

        熟悉SQL的用户一定对聚合不会陌生,简单说任何应用于group by的查询都会执行一个聚合操作。ES的聚合(aggregation)加载和搜索相匹配的文档,并且完成各种统计型的计算。

        ES聚合分为两个主要类别:度量型和桶型。度量型(metric)聚合是指一组文档的统计分析,可以得到诸如最小值、最大值、标准差等度量值。桶(bucket)聚合将匹配的文档切分为一个或多个容器(桶),然后返回每个桶里的文档数量。桶聚合功能上实际就相当于SQL里的group by,SQL中叫组,ES中叫桶。有了桶聚合,可以嵌套其它的聚合,让子聚合在上层聚合所产生的每个文档桶上运行。ES这种所谓的嵌套聚合可以类比于SQL中的group by后面跟多个字段,但更为灵活。看图1的例子。

图1 词条桶型聚合允许在其中嵌套其它聚合

 

        自上而下看图1,如果使用terms聚合获得最为流行的分组标签,同样可以获得每个标签分组的平均成员数量,还可以让ES提供每个标签每年创建的分组数量。用SQL不得不写两个查询来实现:

select tags, count(*), avg(members) from get-togather group by tags;
select tags, year, count(*) from get-togather group by tags, year;

一、聚合的结构

        所有聚合都遵从以下语法规则:

  • 查询的JSON中定义它们,使用键aggregation或aggs标记。需要给每个聚合命名,指定它的类型以及该类型相关的选项。
  • 聚合运行在查询的结果之上。和查询不匹配的文档不会计算在内,除非使用global聚合将不匹配的文档囊括其中。
  • 以进一步过滤查询的结果,而不影响聚合。

(1)聚合请求的结构
        下面的代码执行一个terms聚合,获得get-together中最频繁的标签。

curl '172.16.1.127:9200/get-together/_doc/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "aggs": {                                     # 键aggs表明,这是该请求的聚合部分
    "top_aggs": {                               # 聚合名称
      "terms": {                                # 聚合类型为词条
        "field": "tags.verbatim"                # 未经分析verbatim字段,用于关键字完全匹配,而不是分词匹配
      }
    }
  }
}'

        tags字段的映射为:

"tags" : {
  "type" : "text",
  "fields" : {
    "verbatim" : {
      "type" : "keyword"
    }
  }
}

        查询返回结果的聚合部分如下:

...
  "aggregations" : { 
    "top_aggs" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 6,
      "buckets" : [                             # 每个唯一的词条都是桶里的一个项目
        {
          "key" : "big data",                   # 对于每个词条,可以看到它出现了多少次
          "doc_count" : 3
        },
        {
          "key" : "open source",
          "doc_count" : 3
        },
        
        ...
        
        {
          "key" : "data visualization",
          "doc_count" : 1
        }
      ]
    }
  }

        这个查询没有指定任何匹配条件,它执行match_all查询,所以聚合是在所有文档上进行的。

(2)运行在查询结果上的聚合

curl '172.16.1.127:9200/get-together/_doc/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "location_group": "Denver"
    }
  },
  "aggs": {
    "top_aggs": {
      "terms": {
        "field": "tags.verbatim"
      }
    }
  }
}'

        这个查询带了一个匹配条件。聚合总是在所有和查询匹配的结果上执行,因此查询中的from和size参数对于聚合没有影响。

(3)过滤器和聚合
        聚合只会在与过滤器查询匹配的文档上运行。

curl '172.16.1.127:9200/get-together/_doc/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "location_group": "denver"
        }
      }
    }
  },
  "aggs": {
    "top_aggs": {
      "terms": {
        "field": "tags.verbatim"
      }
    }
  }
}'

        过滤器上聚合的流程如图2所示。

图2 filtered查询所包装的过滤器首先运行,会同时限制结果集合与聚合

 

        还有另一种运行过滤器的方法:使用后过滤器(post filter),该过滤器是在查询结果之后运行,和聚合操作相独立。

curl '172.16.1.127:9200/get-together/_doc/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "post_filter": {
    "term": {
      "location_group": "denver"
    }
  },
  "aggs": {
    "top_aggs": {
      "terms": {
        "field": "tags.verbatim"
      }
    }
  }
}'

        过滤器上聚合的流程如图3所示。

图3 后过滤器在查询之后运行,并不影响聚合

 

        后过滤器和filtered查询中的过滤器有两点不同:

  • 性能:后过滤器是在查询之后运行,确保查询在所有文档上运行。而过滤器只在和查询匹配的文档上运行,整体的请求通常比对等的filtered查询执行更慢,因为filtered查询中过滤器是先运行的,减少了聚合执行时处理的文档数量。
  • 聚合处理的文档集合:如果一篇文档和后过滤器不匹配,它仍然会被聚合操作计算在内。

二、度量集合

        度量聚合从不同文档的分组中提取统计数据,这些统计数据通常来自数值型字段。

(1)统计数据
        通常来说,使用ES查询可以构建一个script字段,在其中放入一小段代码,为每篇文档返回一个数组。在下面的代码中,用脚本计算参与者数量,数值是参与者数组的元素数量。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,                                               # 只关心聚合,返回任何查询结果
  "aggs": {
    "attendees_stats": {
      "stats": {
        "script": "doc['"'attendees'"'].values.length"     # 生成参与者数量的脚本
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "attendees_stats" : {
      "count" : 20,
      "min" : 0.0,
      "max" : 5.0,
      "avg" : 2.9,
      "sum" : 58.0
    }
  }
}

        聚合类型为统计,通过脚本为每篇文档生成一个参与者数量,统计结果包括参与者的最小值、最大值、求和以及平均值,还可以获知这些统计值是从多少文档计算而来的。

        如果只需要这些统计值的其中一项,可以单独请求。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "attendees_avg": {
      "avg": {
        "script": "doc['"'attendees'"'].values.length"
      }
    }
  }
}'

(2)高级统计
        除了使用stats聚合收集统计数据,还可以通过运行extended_stats聚合来获取数值字段的平方值、方差和标准差。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "attendees_extended_stats": {
      "extended_stats": {
        "script": "doc['"'attendees'"'].values.length"
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "attendees_extended_stats" : {
      "count" : 20,
      "min" : 0.0,
      "max" : 5.0,
      "avg" : 2.9,
      "sum" : 58.0,
      "sum_of_squares" : 230.0,
      "variance" : 3.0900000000000007,
      "std_deviation" : 1.7578395831246947,
      "std_deviation_bounds" : {
        "upper" : 6.415679166249389,
        "lower" : -0.6156791662493895
      }
    }
  }
}

        查询生成了和其匹配的文档集合,而所有统计数据都是通过该文档集合中的数值计算而来,所以总是具有100%的准确性。

(3)近似统计
        某些统计可以通过查看文档中的某些数值,良好地进行计算,尽管不是100%准确,这将会减少执行的时间和内存消耗。下面的代码计算参与者的80百分位和99百分位。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "attendees_percentiles": {
      "percentiles": {
        "script": "doc['"'attendees'"'].values.length",
        "percents": [80, 99]
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "attendees_percentiles" : {
      "values" : {
        "80.0" : 4.0,                           # 80%的值不超过4
        "99.0" : 5.0                            # 99%的值不超过5
      }
    }
  }
}

        还有一个percentile_ranks聚合,允许指定一组值,获得相应的文档百分比,而这些文档拥有所指定的值。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "attendees_percentile_ranks": {
      "percentile_ranks": {
        "script": "doc['"'attendees'"'].values.length",
        "values": [4, 5]
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "attendees_percentile_ranks" : {
      "values" : {
        "4.0" : 85.0,
        "5.0" : 100.0
      }
    }
  }
}

        基数(cardinality)是某个字段中唯一值的数量。下面的代码获得去重后的会员数量。

curl -X PUT "172.16.1.127:9200/get-together/_mapping/_doc?pretty" -H 'Content-Type: application/json' -d'
{
  "properties": {
    "members": { 
      "type":     "text",
      "fielddata": true
    }
  }
}'

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "members_cardinality": {
      "cardinality": {
        "field": "members"
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "members_cardinality" : {
      "value" : 8
    }
  }
}

        cardinality聚合是近似的,其内存使用量是一个常数,可以通过precision_threshold参数配置。阈值越高,结果越精确,但是消耗的内存越多。很多情况下,默认的precision_threshold就能很好的运作,因为它提供了内存使用量的准确率之间一个良好的均衡,并且它会根据桶的数量自我调节。

三、桶型聚合

        度量型聚合是获取所有的文档,并且生成一个或多个描述它们的数值。桶型聚合是将文档放入不同的桶中(类似SQL中的group by),对于每个桶,获得一个或多个数值来描述这个桶。用户可以将匹配查询的文档作为一个大桶。

(1)terms聚合
        terms聚合统计的是每个词条,而不是整个字段,因此通常需要在一个非分析型的字段上运行这种聚合。默认情况下,词条的顺序是由词频决定的,并且降序排列。也可以按照词频升序或词条本身排序。下面代码中使用order属性,将标签按字母顺序排列。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "tags": {
      "terms": {
        "field": "tags.verbatim",
        "order": {
          "_term": "asc"
        }
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "tags" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 10,
      "buckets" : [
        {
          "key" : "apache lucene",
          "doc_count" : 1
        },
        {
          "key" : "big data",
          "doc_count" : 3
        },
        
        ...
        
        {
          "key" : "hadoop",
          "doc_count" : 1
        }
      ]
    }
  }

        默认地,terms聚合会返回按序排列的前10个词条。可以通过size参数来修改这个数量。将size设置为0,将获得全部词条,但对于基数很高的字段,这样做是非常危险的,因为返回一个巨大的结果集要消耗大量CPU资源来排序,而且还可能阻塞网络。

        为了得到前10个词条(或者是通过size所配置的数量),ES从每个分片获取一定数量的词条(可以通过shard_size配置)并且将这些结果聚集起来,整个过程如图4所示。为清楚起见,shard_size和size都设置为2。

图4 有的时候,整体的前X个是不准确的,因为每个分片上只有前X个词条被返回了

 

        这种处理机制逻辑上是有明显缺陷的,对于某些未能在单个分片上名列前茅的词条(如图4中的lucene:7),很可能得不到正确的结果。通过加大shared_size的值,可以获得更准确的结果,如图5所示。但这样做使得聚集操作更为昂贵(尤其是在将它们嵌套起来时),因为内存中需要保存更多的桶。

图5 通过增加shared_size的值提高准确性

 

        聚合返回头部的值可以用来判断结果的准确性。

"tags" : {
  "doc_count_error_upper_bound" : 0,
  "sum_other_doc_count" : 10,

        第一个数值是最坏情况下,错误的上限。例如,如果一个分片返回的词条最小词频为5,那么分片中出现4次的词条可能就会被遗漏。如果词条应该出现在最终结果中,那么最坏情况下的错误为4。所有分片的这些数值之和组成了doc_count_error_upper_bound。将show_term_doc_count_error设置为true,就可以获得每个词条的doc_count_error_upper_bound值。这会统计每个词条最坏情况下的错误。

        第二个数值是未能排名靠前的词条之总数量。

        可以使用include和exclude选项,在结果中包含特定的词,或者将特定的词从结果中剔除。单独使用include选项,只会包含匹配某个模式的词条;单独使用exclude选项,只会包含那些不匹配的词条。同时使用两者,exclude会有优先权:包含的词条会匹配include选项设置的模式,但是不会匹配exclude选项设置的模式。下面代码展示了如何只返回“search”的标签计数器。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "tags": {
      "terms": {
        "field": "tags.verbatim",
        "include": ".*search.*"
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "tags" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "elasticsearch",
          "doc_count" : 2
        },
        {
          "key" : "enterprise search",
          "doc_count" : 1
        }
      ]
    }
  }

(2)significant_terms聚合
        significant_terms聚合同terms聚合类似,也会统计词频。但是结果桶按照某个分数来排序,该分数代表了前台文档与背景文档之间的百分比差异。前台文档是那些与查询匹配的文档,而背景文档是当前索引中所有的文档。

        下面的代码将发现哪些get-together的用户,和Lee有着类似的活动品味。为了实现这一点,将查询Lee所参加的活动(前台文档),然后使用significant_terms聚合来看看和整体所参加的活动(背景文档)相比,这些活动(前台文档)中哪些参与者出现得更频繁。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "query": {
    "match": {
      "attendees": "lee"                        # 前台文档是Lee所参与的活动
    }
  },
  "aggs": {
    "significant_attendees": {
      "significant_terms": {                    # 需要在这些活动中,相对于整体而言出现更频繁的参与者
        "field": "attendees",
        "min_doc_count": 2,                     # 只考虑至少参加2个活动的参与者
        "exclude": "lee"                        # 排除Lee本身
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "significant_attendees" : {
      "doc_count" : 5,                          # 所有Lee参加的活动数量是5
      "bg_count" : 20,                          # 总活动数是20
      "buckets" : [
        {
          "key" : "greg",                       # Greg参与了3个活动,全部和Lee一起参与
          "doc_count" : 3,
          "score" : 1.7999999999999998,
          "bg_count" : 3
        },
        {
          "key" : "mike",                       # Mike参与了两个活动,全部和Lee一起参与
          "doc_count" : 2,
          "score" : 1.2000000000000002,
          "bg_count" : 2
        },
        {
          "key" : "daniel",                     # Daniel参与了两个活动,其中有两个和Lee一起参与
          "doc_count" : 2,
          "score" : 0.6666666666666667,
          "bg_count" : 3
        }
      ]
    }
  }

        significant_terms聚合用SQL实现会相当复杂,而且很难保证性能。

(3)range聚合
        下面的代码统计参与者少于4个、至少4个但少于6个、至少6个这三种情况的文档数量。注意范围是个左闭右开的区间,即桶包含了键from的值,但不包含键to的值。范围不必是连续的,它们可以是分离的或者重叠的。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "attendees_breakdown": {
      "range": {
        "script": "doc['"'attendees'"'].values.length",
        "ranges": [
          { "to": 4 },
          { "from": 4, "to": 6 },
          { "from": 6 }
        ]
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "attendees_breakdown" : {
      "buckets" : [
        {
          "key" : "*-4.0",
          "to" : 4.0,
          "doc_count" : 9
        },
        {
          "key" : "4.0-6.0",
          "from" : 4.0,
          "to" : 6.0,
          "doc_count" : 11
        },
        {
          "key" : "6.0-*",
          "from" : 6.0,
          "doc_count" : 0
        }
      ]
    }
  }

        类似的SQL可能是下面这样:

select case when c < 4 then 1 else 0 end c4,
       case when c >=4 and c < 6 then 1 else 0 end c4_6,
       case when c > 6 then 1 else 0 end c6
  from (select count(attendees) c, event_id
          from get-together
         group by event_id) t1;

 (4)date_range聚合
        date_range聚合与range聚合一样运作,除了放在范围定义中的是日期字符串。下面代码将活动分为两个分类,即2013年7月之前和之后开始的。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "dates_breakdown": {
      "date_range": {
        "field": "date",
        "format": "YYYY.MM",                    # 定义日期格式
        "ranges": [
          { "to": "2013.07" },
          { "from": "2013.07" }
        ]
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "dates_breakdown" : {
      "buckets" : [
        {
          "key" : "*-2013.07",
          "to" : 1.3726368E12,
          "to_as_string" : "2013.07",
          "doc_count" : 8                       # 返回每个范围的文档数量
        },
        {
          "key" : "2013.07-*",
          "from" : 1.3726368E12,
          "from_as_string" : "2013.07",
          "doc_count" : 7
        }
      ]
    }
  }

(5)histogram聚合
        histogram聚合不用手动定义每个范围,而是定义一个固定的距离,ES会自动构建多个范围。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "attendees_histogram": {
      "histogram": {
        "script": "doc['"'attendees'"'].values.length",
        "interval": 1                           # 用于构建范围的间距
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "attendees_histogram" : {
      "buckets" : [
        {
          "key" : 0.0,                          # key显示了范围的起始值。终止值是起始值加上间距值
          "doc_count" : 5
        },
        {
          "key" : 1.0,                          # 下一个起始值就是前一个的终止值
          "doc_count" : 0
        },
        {
          "key" : 2.0,
          "doc_count" : 0
        },
        {
          "key" : 3.0,
          "doc_count" : 4
        },
        {
          "key" : 4.0,
          "doc_count" : 9
        },
        {
          "key" : 5.0,
          "doc_count" : 2
        }
      ]
    }
  }

(6)date_histogram聚合

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "event_dates": {
      "date_histogram": {
        "field": "date",
        "interval": "1M"                        # 间距被指定为日期字符串
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "event_dates" : {
      "buckets" : [
        {
          "key_as_string" : "2013-02-01T00:00",
          "key" : 1359676800000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2013-03-01T00:00",
          "key" : 1362096000000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2013-04-01T00:00",
          "key" : 1364774400000,
          "doc_count" : 2
        },
        {
          "key_as_string" : "2013-05-01T00:00",
          "key" : 1367366400000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2013-06-01T00:00",
          "key" : 1370044800000,
          "doc_count" : 3
        },
        {
          "key_as_string" : "2013-07-01T00:00",
          "key" : 1372636800000,
          "doc_count" : 5
        },
        {
          "key_as_string" : "2013-08-01T00:00",
          "key" : 1375315200000,
          "doc_count" : 0
        },
        {
          "key_as_string" : "2013-09-01T00:00",
          "key" : 1377993600000,
          "doc_count" : 2
        }
      ]
    }
  }

四、嵌套聚合

1. 多桶聚合

        图6所示的例子中,terms聚合允许用户展示get-together的热门标签,这意味着将每个标签创建一个文档桶。然后使用子聚合统计对于每个标签,每个月创建了多少文档。

图6 在terms聚合中嵌套date_histogram聚合

 

        下面的代码是一个三层嵌套聚合的例子。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {                                     # 词条聚合,获取标签
    "top_tags": {
      "terms": {
        "field": "tags.verbatim"
      },
      "aggs": {                                 # 对每个标签运行一次日期直方图子聚合
        "groups_per_month": {
          "date_histogram": {
            "field": "created_on",
            "interval": "1M"
          },
          "aggs": {                             # 对于标签 + 月份的桶,运行范围聚合
            "number_of_members": {
              "range": {
                "script": "doc['"'attendees'"'].values.length",
                "ranges": [
                  {
                    "to": 3
                  },
                  {
                    "from": 3
                  }
                ]
              }
            }
          }
        }
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : { 
    "top_tags" : { 
      "buckets" : [ { 
        "key" : "big data",                     # big data包含3篇文档
        "doc_count" : 3, 
        "groups_per_month" : { 
          "buckets" : [ {                       # 根据big data文档创建月份产生的桶
            "key_as_string" : "2010-04-01", 
            "key" : 1270080000000, 
            "doc_count" : 1,                    # 这篇文档是2010年4月创建的
            "number_of_members" : {
              "buckets" : [ 
               {
                  "key" : "*-3.0",
                  "to" : 3.0,
                  "to_as_string" : "3.0", 
                  "doc_count" : 1               # 这篇文档包含的参与者少于3
               }, 
               {
                  "key" : "3.0-*",
                  "from" : 3.0,
                  "from_as_string" : "3.0",
                  "doc_count" : 0
               } ]
             }
           }, 
           {
             "key_as_string" : "2012-08-01",    # big data下一个桶创建的是2012年8月的
[...]

        类似的SQL可能是下面这样:

select count(1),tags,ym,case when ac < 3 then 0 else 1 end
  from (select tags,
               year(created_on) * 100 + month(created_on) ym,
               length() - length(replace(attendees, ',', '')) ac
          from together) t1
 group by tags , ym , case when ac < 3 then 0 else 1 end

        下面的代码在attendees字段上运行了terms聚合,并在其中嵌套了top_hits聚合。它将展示最为活跃的参与者所参加的活动。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "frequent_attendees": {
      "terms": {                                # 词条聚合返回参与活动最多的两个用户
        "field": "attendees",
        "size": 2
      },
      "aggs": {
        "recent_events": {
          "top_hits": {                         # top_hits聚合返回实际的活动
            "sort": {
              "date": "desc"                    # 最近的活动排在前面
            },
            "_source": {
              "include": [                      # 选择要包含的字段
                "title"
              ]
            },
            "size": 1                           # 限制每个桶的结果数量
          }
        }
      }
    }
  }
}'

        返回结果如下:

...
  "aggregations" : {
    "frequent_attendees" : {
      "doc_count_error_upper_bound" : 1,
      "sum_other_doc_count" : 50,
      "buckets" : [
        {
          "key" : "lee",                        # Lee是最活跃的会员,参加了5个活动
          "doc_count" : 5,
          "recent_events" : {
            "hits" : {
              "total" : 5,
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "get-together",
                  "_type" : "_doc",
                  "_id" : "100",
                  "_score" : null,
                  "_routing" : "1",
                  "_source" : {
                    "title" : "Liberator and Immutant"
                  },
                  "sort" : [
                    1378404000000
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : "andy",
          "doc_count" : 3,
          "recent_events" : {
            "hits" : {
              "total" : 3,
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "get-together",
                  "_type" : "_doc",
                  "_id" : "110",
                  "_score" : null,
                  "_routing" : "4",
                  "_source" : {
                    "title" : "Big Data and the cloud at Microsoft"
                  },
                  "sort" : [
                    1375293600000
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }

        类似的SQL可能是下面这样:

select attendees, id, titile
  from (select t1.attendees, 
               t1.id, 
               t1.titile, 
               row_number() over (partition by t1.attendees order by t1.date1 desc) r 
          from get-together t1, 
               (select * 
                  from (select attendees, count(*) c 
                          from get-together 
                            group by attendees 
                         order by count(*) desc) 
                 where rownum <=2) t2 
         where t1.attendees=t2.attendees 
         order by t2.c desc) 
 where r=1;

2. 单桶聚合

        默认情况下,ES在查询结果上运行聚合。如果想要改变这种默认行为,将要使用单桶聚合。

(1)global聚合
        聚合创建一个桶,包含索引中的全部文档。当用户希望无论何种查询,都在所有文档上运行聚合时,这一点非常有用。如图7所示,想看整体的标签,并且和用户搜索无关。

图7 将其它聚合嵌套在global聚合之中,让它们可以在全部文档上运行

 

        下面的代码中,将terms聚合嵌套在global聚合里,以此获得所有文档中的标签,即使查询只是查找了标题里含有“elasticsearch”字样的文档。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "query": {
    "match": {
      "name": "elasticsearch"
    }
  },
  "aggs": {
    "all_documents": {
      "global": {},                             # 全局聚合是父聚合
      "aggs": {
        "top_tags": {
          "terms": {                            # 词条聚合嵌套其中,将会运行在所有的数据上
            "field": "tags.verbatim"
          }
        }
      }
    }
  }
}'

        返回结果如下:

{
  "took" : 24,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,                                # 查询返回两篇文档,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "all_documents" : {
      "doc_count" : 20,                         # 聚合运行在所有20篇文档上
      "top_tags" : {
        "doc_count_error_upper_bound" : 0,
        "sum_other_doc_count" : 6,
        "buckets" : [
          {
            "key" : "big data",
            "doc_count" : 3
          },
          {
            "key" : "open source",
            "doc_count" : 3
          },
          {
            "key" : "denver",
            "doc_count" : 2
          },
          {
            "key" : "elasticsearch",
            "doc_count" : 2
          },
          {
            "key" : "lucene",
            "doc_count" : 2
          },
          {
            "key" : "solr",
            "doc_count" : 2
          },
          {
            "key" : "apache lucene",
            "doc_count" : 1
          },
          {
            "key" : "clojure",
            "doc_count" : 1
          },
          {
            "key" : "cloud computing",
            "doc_count" : 1
          },
          {
            "key" : "data visualization",
            "doc_count" : 1
          }
        ]
      }
    }
  }
}

(2)filter聚合
        filter聚合限制了聚合所统计的文档,而不影响查询结果,如图8所示。

图8 对于嵌套其中的子聚合,过滤器聚合限制了该子聚合统计的查询结果。


        为了实现这一点,下面代码运行了一个查询,并加上了聚合。首先拥有一个filter聚合,将文档集合限制在7月1日之后,然后在其中嵌套了terms聚合。

curl -X PUT "172.16.1.127:9200/get-together/_mapping/_doc?pretty" -H 'Content-Type: application/json' -d'
{
  "properties": {
    "description": { 
      "term_vector": "with_positions_offsets",
      "type":     "text",
      "fielddata": true
    }
  }
}'

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  },
  "aggs": {
    "since_july": {
      "filter": {                               # 过滤器查询定义了一个桶,子聚合将在这个桶上运行
        "range": {
          "date": {
            "gt": "2013-07-01T00:00"
          }
        }
      },
      "aggs": {
        "description_cloud": {
          "terms": {
            "field": "description"
          }
        }
      }
    }
  }
}'

        返回结果如下:

{
  "took" : 42,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 7,                                # 查询返回了7条文档
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "since_july" : {
      "meta" : { },
      "doc_count" : 2,                          # 而description_cloud过滤器只在和过滤器匹配的两个结果上运行
      "description_cloud" : {
        "doc_count_error_upper_bound" : 1,
        "sum_other_doc_count" : 16,
        "buckets" : [
          {
            "key" : "we",
            "doc_count" : 2
          },
          {
            "key" : "with",
            "doc_count" : 2
          },
          {
            "key" : "a",
            "doc_count" : 1
          },
          {
            "key" : "about",
            "doc_count" : 1
          },
          {
            "key" : "and",
            "doc_count" : 1
          },
          {
            "key" : "at",
            "doc_count" : 1
          },
          {
            "key" : "big",
            "doc_count" : 1
          },
          {
            "key" : "can",
            "doc_count" : 1
          },
          {
            "key" : "crunching",
            "doc_count" : 1
          },
          {
            "key" : "data",
            "doc_count" : 1
          }
        ]
      }
    }
  }
}

(3)missing聚合
        missing聚合创建的桶包含了那些缺乏某个特定字段的文档。举例来说,可能有一个运行在活动日期字段的date_histogram聚合,但是某些活动还没有日期。用户可以通过missing聚合来统计它们。

URI=172.16.1.127:9200/get-together/_doc/_search
curl "$URI?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "event_dates": {
      "date_histogram": {
        "field": "date",
        "interval": "1M"
      }
    },
    "missing_date": {
      "missing": {
        "field": "date"
      }
    }
  }
}'

        返回结果如下:
 

{
  "took" : 20,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 20,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "event_dates" : {
      "buckets" : [
        {
          "key_as_string" : "2013-02-01T00:00",
          "key" : 1359676800000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2013-03-01T00:00",
          "key" : 1362096000000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2013-04-01T00:00",
          "key" : 1364774400000,
          "doc_count" : 2
        },
        {
          "key_as_string" : "2013-05-01T00:00",
          "key" : 1367366400000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2013-06-01T00:00",
          "key" : 1370044800000,
          "doc_count" : 3
        },
        {
          "key_as_string" : "2013-07-01T00:00",
          "key" : 1372636800000,
          "doc_count" : 5
        },
        {
          "key_as_string" : "2013-08-01T00:00",
          "key" : 1375315200000,
          "doc_count" : 0
        },
        {
          "key_as_string" : "2013-09-01T00:00",
          "key" : 1377993600000,
          "doc_count" : 2
        }
      ]
    },
    "missing_date" : {
      "doc_count" : 5
    }
  }
}

 

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值