范围限定的聚合
测试数据:所有聚合的例子到目前为止,你可能已经注意到,我们的搜索请求省略了一个 query
。 整个请求只不过是一个聚合。
聚合可以与搜索请求同时执行,但是我们需要理解一个新概念: 范围 。 默认情况下,聚合与查询是对同一范围进行操作的,也就是说,聚合是基于我们查询匹配的文档集合进行计算的。
让我们看看第一个聚合的示例:
GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"colors" : {
"terms" : {
"field" : "color"
}
}
}
}
我们可以看到聚合是隔离的。现实中,Elasticsearch 认为 "没有指定查询" 和 "查询所有文档" 是等价的。前面这个查询内部会转化成下面的这个请求:
GET /cars/transactions/_search
{
"size" : 0,
"query" : {
"match_all" : {}
},
"aggs" : {
"colors" : {
"terms" : {
"field" : "color"
}
}
}
}
因为聚合总是对查询范围内的结果进行操作的,所以一个隔离的聚合实际上是在对
match_all
的结果范围操作,即所有的文档。
一旦有了范围的概念,我们就能更进一步对聚合进行自定义。我们前面所有的示例都是对 所有 数据计算统计信息的:销量最高的汽车,所有汽车的平均售价,最佳销售月份等等。
利用范围,我们可以问“福特在售车有多少种颜色?”诸如此类的问题。可以简单的在请求中加上一个查询(本例中为 match
查询):
GET /cars/transactions/_search
{
"query" : {
"match" : {
"make" : "ford"
}
},
"aggs" : {
"colors" : {
"terms" : {
"field" : "color"
}
}
}
}
因为我们没有指定
"size" : 0
,所以搜索结果和聚合结果都被返回了:
{
...
"hits": {
"total": 2,
"max_score": 1.6931472,
"hits": [
{
"_source": {
"price": 25000,
"color": "blue",
"make": "ford",
"sold": "2014-02-12"
}
},
{
"_source": {
"price": 30000,
"color": "green",
"make": "ford",
"sold": "2014-05-18"
}
}
]
},
"aggregations": {
"colors": {
"buckets": [
{
"key": "blue",
"doc_count": 1
},
{
"key": "green",
"doc_count": 1
}
]
}
}
}
看上去这并没有什么,但却对高大上的仪表盘来说至关重要。 加入一个搜索栏可以将任何静态的仪表板变成一个实时数据搜索设备。 这让用户可以搜索数据,查看所有实时更新的图形(由于聚合的支持以及对查询范围的限定)。 这是 Hadoop 无法做到的!
Java代码实现:
/**
* Description:带有搜索条件的聚合查询
* 例:bmw(宝马)在售车有多少种颜色
*
* @author wangweidong
* CreateTime: 2017年11月10日 下午4:03:30
*
*/
@Test
public void searchBucketsAggregation() {
String index = "cars";
String type = "transactions";
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index).setTypes(type);
//搜索条件
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.matchQuery("make", "bmw"));
//聚合
TermsAggregationBuilder field = AggregationBuilders.terms("popular_colors").field("color.keyword");
searchRequestBuilder.addAggregation(field);
// searchRequestBuilder.setSize(0);
searchRequestBuilder.setQuery(queryBuilder);
SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
System.out.println(searchResponse.toString());
Terms genders = searchResponse.getAggregations().get("popular_colors");
for (Terms.Bucket entry : genders.getBuckets()) {
Object key = entry.getKey(); // Term
Long count = entry.getDocCount(); // Doc count
System.out.println(key);
System.out.println(count);
}
}
全局桶
通常我们希望聚合是在查询范围内的,但有时我们也想要搜索它的子集,而聚合的对象却是 所有 数据。
例如,比方说我们想知道福特汽车与 所有 汽车平均售价的比较。我们可以用普通的聚合(查询范围内的)得到第一个信息,然后用 全局
桶获得第二个信息。
全局
桶包含 所有 的文档,它无视查询的范围。因为它还是一个桶,我们可以像平常一样将聚合嵌套在内:
GET /cars/transactions/_search
{
"size" : 0,
"query" : {
"match" : {
"make" : "ford"
}
},
"aggs" : {
"single_avg_price": {
"avg" : { "field" : "price" }
},
"all": {
"global" : {},
"aggs" : {
"avg_price": {
"avg" : { "field" : "price" }
}
}
}
}
}
},
"all": {
"global" : {},
"aggs" : {
"avg_price": {
"avg" : { "field" : "price" }
}
}
}
}
}
| 聚合操作在查询范围内(例如:所有文档匹配 |
|
|
| 聚合操作针对所有文档,忽略汽车品牌。 |
single_avg_price
度量计算是基于查询范围内所有文档,即所有 福特
汽车。avg_price
度量是嵌套在 全局
桶下的,这意味着它完全忽略了范围并对所有文档进行计算。聚合返回的平均值是所有汽车的平均售价。
如果能一直坚持读到这里,应该知道我们有个真言:尽可能的使用过滤器。它同样可以应用于聚合,在下一章中,我们会展示如何对聚合结果进行过滤而不是仅对查询范围做限定。
Java代码实现:
/**
* Description:全局桶
* 例:想知道福特(ford)汽车与 所有 汽车平均售价的比较
*
* @author wangweidong
* CreateTime: 2017年11月10日 下午4:03:30
*
*/
@Test
public void globalBucketsAggregation() {
try {
String index = "cars";
String type = "transactions";
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index).setTypes(type);
//搜索条件
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.matchQuery("make", "ford"));
//平均价格
AvgAggregationBuilder singleAvgPrice = AggregationBuilders.avg("single_avg_price").field("price");
//聚合
GlobalAggregationBuilder field = AggregationBuilders.global("all");
AvgAggregationBuilder avgPrice = AggregationBuilders.avg("avg_price").field("price");
field.subAggregation(avgPrice);
searchRequestBuilder.addAggregation(singleAvgPrice);
searchRequestBuilder.addAggregation(field);
searchRequestBuilder.setSize(0);
searchRequestBuilder.setQuery(queryBuilder);
SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
System.out.println(searchResponse.toString());
Avg avg = searchResponse.getAggregations().get("single_avg_price");
System.out.println("福特(ford)汽车平均价格:" + avg.getValue());
Global global = searchResponse.getAggregations().get("all");
Avg allAvg = global.getAggregations().get("avg_price");
System.out.println("所有汽车平均价格:" + allAvg.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}