博爱网站建设,株洲人才网,网站建设福州最好,小程序定制 seo营销当用户使用搜索引擎完成搜索后#xff0c;在展示结果中需要进行进一步的筛选#xff0c;而筛选的维度需要根据当前的搜索结果进行汇总#xff0c;这就用到了聚合技术。聚合的需求在很多应用程序中都有所体现#xff0c;例如在京东App中搜索“咸鸭蛋”#xff0c;然后单击搜…当用户使用搜索引擎完成搜索后在展示结果中需要进行进一步的筛选而筛选的维度需要根据当前的搜索结果进行汇总这就用到了聚合技术。聚合的需求在很多应用程序中都有所体现例如在京东App中搜索“咸鸭蛋”然后单击搜索界面中的“筛选”按钮在弹出的界面中可以对当前的搜索结果进行进一步的过滤。例如可以从价格区间、品牌、分类、枚数等维度分别进行筛选如图7.1所示。 ES支持丰富的聚合操作不仅可以使用聚合功能对文档进行计数还可以计算文档字段的平均值、最大值和最小值等。ES还提供了桶聚合的功能以便于对多维度数据进行聚合。本章将结合实例介绍这些内容。另外如果希望搜索结果和聚合结果一起返回其中绕不开的一个主题就是分页和排序本章也会对这两部内容进行介绍。
为方便介绍下面重新定义酒店的索引。
PUT /hotel
{ settings: { number_of_shards: 1 }, mappings: { properties: { title: { type: text }, city: { type: keyword }, price: { type: double }, create_time: { type: keyword }, full_room: { type: boolean },tags: { type: keyword }} }
}接着向索引中写入示例数据具体如下
POST /_bulk
{index:{_index:hotel,_id:001}}
{title: 文雅酒假日酒店,city:北京,price: 556.00,create_time: 20200418120000,full_room:true, tags:[wifi,小型电影院],comment_info:{favourable_comment:20, negative_comment:10}}
{index:{_index:hotel,_id:002}}
{title: 金都嘉怡假日酒店,city:北京,create_time:20210315200000, full_room:false,tags: [wifi,免费早餐],comment_info:{favourable_comment:20,negative_ comment:10}}
{index:{_index:hotel,_id:003}}
{title: 金都假日酒店,city:北京,price: 200.00,create_time: 20210509160000,full_room:true,comment_info:{favourable_comment:20,negative_comment:10}}
{index:{_index:hotel,_id:004}}
{title: 金都假日酒店,city:天津,price: 500.00,create_time: 20210218080000,full_room:false,tags:[wifi,免费车位]}
{index:{_index:hotel,_id:005}}
{title: 文雅精选酒店,city:天津,price: 800.00,create_time: 20210101080000,full_room:true,tags:[wifi,充电车位],comment_info:{favourable_comment :20,negative_comment:10}}1 聚合指标
在进行聚合搜索时聚合的指标业务需求不仅是文档数量。例如在酒店搜索场景中我们希望能看到以当前位置为中心点周边各个区域酒店的平均价格。本节将对ES支持的聚合指标进行介绍。
在搜索聚合时用户可能会关注字段的相关统计信息例如平均值、最大值、最小值及加和值等。例如用户在使用一个二手房交易搜索引擎进行搜索时可能会关注当前城市各个区域的房产平均价格。再例如用户在搜索酒店时也可能会关注附近各个区域酒店的最低价格。如图7.2所示左图为在链家App的地图模式可以搜索到当前位置附近的二手房平均交易价格右图为携程App的地图模式可以搜索到当前位置附近最低价格的酒店。 ES聚合请求的地址也是索引的搜索地址可以使用aggs子句封装聚合请求。
当使用avg子句进行平均值的聚合时可以在avg子句中指定聚合的字段。在默认情况下查询将匹配所有文档如果不需要返回匹配的文档信息最好将返回的文档个数设置为0。这样既可以让结果看起来更整洁又可以提高查询速度。下面的DSL将查询所有酒店的平均价格并且不返回匹配的文档信息。
GET /hotel/_search
{ size: 0, aggs: { my_agg: { //聚合名称 avg: { field: price //计算文档的平均价格 } } }
} 在上面的DSL中设定avg聚合的字段为price字段并设置size参数的值为0。该DSL被执行后ES的返回结果如下
{ … hits : { //命中的文档列表 total : { value : 5, relation : eq }, max_score : null, hits : [ ] }, aggregations : { //聚合结果 my_agg : { //聚合名称 value : 514.0 //聚合指标值 } }
} 在上面的搜索结果中索引中的5个文档全部命中由于DSL设置size为0所以命中文档的信息没有显示。在搜索结果的aggregations子句中存储着聚合结果其中my_agg是聚合的名称其对应的value值就是具体聚合结果即酒店的平均价格。 如果聚合的指标字段不是ES的基本类型例如object类型则可以使用点运算符进行引用。下面的DSL演示了该用法
GET /hotel/_search
{ size: 0, aggs: { my_agg: { avg: { //使用点运算符引用object类型字段的数据 field: comment_info.favourable_comment } } }
} 与平均值类似最大值、最小值及加和值分别使用max、min和sum子句进行聚合这里不再赘述。
以下代码演示了在Java中使用聚合计算平均值的逻辑。
public void getAvgAggSearch() throws IOException{ //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String aggNamemy_agg; //聚合的名称 //定义avg聚合指定字段为price AvgAggregationBuilder aggregationBuilder AggregationBuilders.avg
(aggName).field(price); searchSourceBuilder.aggregation(aggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Avg avg aggregations.get(aggName); //获取avg聚合返回的对象 String keyavg.getName(); //获取聚合名称 double avgValue avg.getValue(); //获取聚合值 System.out.println(keykey,avgValueavgValue); //打印结果
} 为了避免多次请求ES还提供了stats聚合。stats聚合可以将对应字段的最大值、最小值、平均值及加和值一起计算并返回计算结果。下面的DSL展示了stats的用法。
GET /hotel/_search
{ size: 0, aggs: { my_agg: { stats: { //使用stats运算符计算多个指标 field: price } } }
} 在上面的DSL中对所有酒店进行了常用统计指标的聚合查询结果如下
{ … hits : { total : { value : 5, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { count : 4, //文档数量 min : 200.0, //聚合的价格最小值 max : 800.0, //聚合的价格最大值 avg : 514.0, //聚合的价格平均值 sum : 2056.0 //聚合的价格加和值 } }
} 以下代码演示了在Java中使用stats聚合的逻辑。
public void getStatsAggSearch () throws IOException{ //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String aggNamemy_agg; //聚合的名称 //定义stats聚合指定字段为price StatsAggregationBuilder aggregationBuilder AggregationBuilders.stats
(aggName).field(price); searchSourceBuilder.aggregation(aggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Stats stats aggregations.get(aggName); //获取stats聚合返回的对象 String keystats.getName(); //获取聚合名称 double sumVal stats.getSum() ; //获取聚合加和值 double avgVal stats.getAvg() ; //获取聚合平均值 long countVal stats.getCount() ; //获取聚合文档数量值 double maxVal stats.getMax() ; //获取聚合最大值 double minVal stats.getMin() ; //获取聚合最小值 System.out.println(keykey); //打印聚合名称 System.out.println(sumValsumVal,avgValavgVal,countVal
countVal,maxValmaxVal,minValminVal); //打印结果
} 2 空值处理
在索引中的一部分文档很可能其某些字段是缺失的在介绍空值处理之前首先介绍ES聚合查询提供的value_count聚合该聚合用于统计字段非空值的个数。以下示例使用value_count聚合统计了price字段中非空值的个数。
GET /hotel/_search
{ size: 0, aggs: { my_agg: { value_count: { //统计price字段中非空值的个数 field: price } } }
} 执行上述DSL后ES返回的结果如下
{ .. hits : { total : { value : 5, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { //price字段中非空值的个数 value : 4 } }
} 通过上述结果可以看到当前索引中price字段中的非空值有4个。 下面的代码演示了在Java中使用value_count对price字段进行聚合的逻辑。
public void getValueCountAggSearch () throws IOException{ //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String aggNamemy_agg; //聚合的名称 //定义value_count聚合指定字段为price ValueCountAggregationBuilder aggregationBuilder AggregationBuilders.
count(aggName).field(price); aggregationBuilder.missing(200); searchSourceBuilder.aggregation(aggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); //获取value_count聚合返回的对象 ValueCount valueCount aggregations.get(aggName); String keyvalueCount.getName(); //获取聚合名称 long count valueCount.getValue(); //获取聚合值 System.out.println(keykey,countcount); //打印结果
} 需要指出的是如果判断的字段是数组类型则value_count统计的是符合条件的所有文档中该字段数组中非空元素个数的总和而不是数组的个数总和。下面的DSL用于统计tags字段数组中非空元素个数的总和。
GET /hotel/_search
{ size: 0, aggs: { my_agg: { value_count: { //统计tags字段数组中非空元素的个数 field: tags } } }
} 在索引的5个文档中除去文档003没有tags字段外其他4个文档的tags字段数组中各有两个元素因此聚合的值为2×48个。下面来看一下ES返回的内容
{ …. hits : { total : { value : 5, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { value : 8 //tags字段数组中非空元素的个数 } }
} 上面的结果中aggregations.my_agg.value的值为8这和前面计算的数值相等验证了使用value_count对数组字段进行聚合时ES返回的结果是所有数组元素的个数总和这一结论。
如果需要以空值字段的数据作为聚合指标对其进行聚合可以在指标统计中通过missing参数指定填充值对空值进行填充。以下示例演示了对price字段进行聚合并设定了当字段值为空值时使用100进行替代的查询请求。
GET /hotel/_search
{ size: 0, aggs: { my_agg: { sum: { field: price, missing:100 //计算加和值时将price字段中的空值用100代替 } } }
} 在索引中文档002的price字段为空因此被填充为100文档001、003、004和005的price字段分别为556、200、500和800因此符合聚合的值应该是5561002005008002156。ES返回的结果如下
{ … hits : { total : { value : 5, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { value : 2156.0 //将price字段中的空值用100代替后的加和结果 } }
} 以下代码演示了在Java中当聚合指标为空值时指定填充值的逻辑。
public void getSumAggSearch () throws IOException{ //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String aggNamemy_agg; //聚合的名称 //定义sum聚合指定字段为price SumAggregationBuilder aggregationBuilder AggregationBuilders.sum
(aggName).field(price); aggregationBuilder.missing(100); searchSourceBuilder.aggregation(aggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Sum sum aggregations.get(aggName); //获取sum聚合返回的对象 String keysum.getName(); //获取聚合名称 double sumVal sum.getValue(); //获取聚合值 System.out.println(keykey,countsumVal); //打印结果
} 3 桶聚合
前面介绍的聚合指标是指符合条件的文档字段的聚合有时还需要根据某些维度进行聚合。例如在搜索酒店时按照城市、是否满房、标签和创建时间等维度统计酒店的平均价格。这些字段统称为“桶”在同一维度内有一个或者多个桶。例如城市桶有“北京”“天津”等是否满房桶有“满房”“非满房”。
1 单维度桶聚合
最简单的桶聚合是单维度桶聚合指的是按照一个维度对文档进行分组聚合。在桶聚合时聚合的桶也需要匹配匹配的方式有terms、filter和ranges等。本节只介绍比较有代表性的terms查询和ranges查询对其他匹配方式感兴趣读者可以阅读相关文档进行学习这里不再赘述。
terms聚合是按照字段的实际完整值进行匹配和分组的它使用的维度字段必须是keyword、bool、keyword数组等适合精确匹配的数据类型因此不能对text字段直接使用terms聚合如果对text字段有terms聚合的需求则需要在创建索引时为该字段增加多字段功能。 以下的DSL描述的是按照城市进行聚合的查询
GET /hotel/_search
{ size: 0, aggs: { my_agg: { terms: { //按照城市进行聚合 field: city } } }
} 因为ES支持多桶聚合所以每个桶聚合需要定义一个名字此处定义了一个桶聚合名字为my_agg。在这个桶聚合中使用了一个terms聚合聚合字段选择了城市目的是统计各个城市的酒店的文档个数。在聚合外面因为不希望返回任何文档所以指定查询返回的文档为0。执行该DSL后ES返回的结果如下
{ … hits : { … hits : [ ] }, aggregations : { my_agg : { //单维度聚合的名称 doc_count_error_upper_bound : 0, //可能被遗漏的文档数量的最大值 sum_other_doc_count : 0, //除了返回给用户的文档外剩下的文档总数 buckets : [ //聚合桶 { key : 北京, doc_count : 3 //该聚合桶下的文档数量 }, { key : 天津, doc_count : 2 //该聚合桶下的文档数量 } ] } }
} 在默认情况下进行桶聚合时如果不指定指标则ES默认聚合的是文档计数该值以doc_count为key存储在每一个bucket子句中。在聚合结果的buckets的两个bucket中key字段的值分别为“北京”“天津”表示两个bucket的唯一标识doc_count字段的值分别为3和2表示两个bucket的文档计数。返回的doc_count是近似值并不是一个准确数因此在聚合外围ES给出了两个参考值doc_count_error_upper_bound和sum_other__doc_countdoc_count_error_upper表示被遗漏的文档数量可能存在的最大值sum_other_doc_count表示除了返回给用户的文档外剩下的文档总数。
以下DSL是按照满房状态进行聚合的查询注意该字段是bool型
GET /hotel/_search
{ size: 0, aggs: { my_agg: { terms: { field: full_room //按照满房状态进行聚合 } } }
} 执行DSL后ES返回的结果如下
{ … hits : { total : { value : 5, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ { key : 1, key_as_string : true, //聚合桶的字符串形式 doc_count : 3 }, { key : 0, key_as_string : false, //聚合桶的字符串形式 doc_count : 2 } ] } }
} 从上述结果中可以看到在满房和非满房的bucket结果中多出了一个字段名称为key_as_string其值分别是true和false。另外这两个bucket的key值分别为1和0。这是因为如果桶字段类型不是keyword类型ES在聚合时会将桶字段转换为Lucene存储的实际值进行识别。true在Lucene中存储为1false在Lucene中存储为0这就是为什么满房和非满房的key字段分别为1和0的原因。这种情况给用户的使用带来了一些困惑因为和原始值的差别比较大。针对这个问题我们可以使用ES提供的key_as_string桶识别字段它是原始值的字符串形式和原始值的差别比较小。 以下代码演示了在Java中使用terms聚合进行单维度桶聚合的逻辑
public void getBucketDocCountAggSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String termsAggName my_terms; //指定聚合的名称 //定义terms聚合指定字段为城市 TermsAggregationBuilder termsAggregationBuilder AggregationBuilders.
terms(termsAggName).field(full_room); searchSourceBuilder.aggregation(termsAggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Terms terms aggregations.get(termsAggName); //获取聚合返回的对象 for (Terms.Bucket bucket : terms.getBuckets()) { String bucketKey bucket.getKeyAsString(); //获取桶名称 long docCount bucket.getDocCount(); //获取文档个数 System.out.println(termsKey bucketKey ,docCount docCount); }
} 除了terms聚合ranges聚合也是经常使用的一种聚合。它匹配的是数值字段表示按照数值范围进行分组。用户可以在ranges中添加分组每个分组用from和to表示分组的起止数值。注意该分组包含起始数值不包含终止数值。以下DSL演示了使用ranges进行聚合的方法
GET /hotel/_search
{ size: 0, aggs: { my_agg: { range: { field: price, ranges: [ //多个范围桶 { to: 200 //不指定from默认from为0 }, { from: 200, to: 500 }, { from: 500 //不指定to默认to为该字段最大值 } ] } } }
} 执行上述DSL后ES返回的结果如下
{ … hits : { … hits : [ ] }, aggregations : { my_agg : { //range聚合名称 buckets : [ //范围聚合桶 { key : *-200.0, to : 200.0, doc_count : 0 }, { key : 200.0-500.0, from : 200.0, to : 500.0, doc_count : 1 }, { key : 500.0-*, from : 500.0, doc_count : 3 } ] } }
} 在上面的分组划分中第一个分组规则为price200没有文档与其匹配因此其doc_count为0第二个分组规则为200≤price500文档003与其匹配因此其doc_count为1第三个分组规则为price500文档001、004和005与其匹配因此其doc_count值为3。
以下代码演示了在Java中使用ranges聚合的逻辑
public void getRangeDocCountAggSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String rangeAggName my_range; //聚合的名称 //定义ranges聚合指定字段为price RangeAggregationBuilder rangeAgg AggregationBuilders.range(rangeAggName).
field(price); rangeAgg.addRange(new RangeAggregator.Range(null,null,200d)); rangeAgg.addRange(new RangeAggregator.Range(null,200d,500d)); rangeAgg.addRange(new RangeAggregator.Range(null,500d,null)); searchSourceBuilder.aggregation(rangeAgg); //添加ranges聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Range range aggregations.get(rangeAggName);//获取range聚合返回的对象 for (Range.Bucket bucket : range.getBuckets()) { String bucketKey bucket.getKeyAsString(); //获取桶名称 long docCount bucket.getDocCount(); //获取聚合文档个数 System.out.println(bucketKey bucketKey ,docCount docCount); }
} 有时还需要对单维度桶指定聚合指标聚合指标单独使用子aggs进行封装该aggs子句的使用方式和上一节介绍的聚合指标相同。以下请求表示按照城市维度进行聚合统计各个城市的平均酒店价格
GET /hotel/_search
{ size: 0, aggs: { my_agg: { //单维度聚合名称 terms: { //定义单维度桶 field: city }, aggs: { //用于封装单维度桶下的聚合指标 my_sum: { //聚合指标名称 sum: { //对price字段进行加和 field: price, missing: 200 } } } } }
} 执行上述DSL后ES返回的结果如下
{ … hits : { … }, aggregations : { my_agg : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //单维度桶列表 { //具体的单维度桶 key : 北京, doc_count : 3, my_sum : { //聚合指标 value : 956.0 } }, { //具体的单维度桶 key : 天津, doc_count : 2, my_sum : { //聚合指标 value : 1300.0 } } ] } }
} 在上面的结果中聚合桶的维度是城市当前索引中城市为“北京”的文档个数为3城市为“天津”的文档个数为2。将这两组文档的聚合结果在buckets子句中进行了封装可以根据key字段进行聚合桶的识别每个聚合的组中既有文档个数又有价格的加和值。
以下代码演示了在Java中使用桶聚合和指标聚合的逻辑
public void getBucketAggSearch () throws IOException{ //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String termsAggNamemy_terms; //聚合的名称 //定义terms聚合指定字段为城市 TermsAggregationBuilder termsAggregationBuilder AggregationBuilders.
terms(termsAggName).field(city); String sumAggNamemy_sum; //sum聚合的名称 //定义sum聚合指定字段为价格 SumAggregationBuilder sumAggregationBuilder AggregationBuilders.
sum(sumAggName).field(price); //定义聚合的父子关系 termsAggregationBuilder.subAggregation(sumAggregationBuilder); searchSourceBuilder.aggregation(termsAggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Terms terms aggregations.get(termsAggName); //获取聚合返回的对象 for(Terms.Bucket bucket:terms.getBuckets()){ String termsKeybucket.getKey().toString(); System.out.println(termsKeytermsKey); Sum sum bucket.getAggregations().get(sumAggName); String keysum.getName(); //获取聚合名称 double sumVal sum.getValue(); //获取聚合值 System.out.println(keykey,countsumVal); //打印结果 }
} 2 多维度桶嵌套聚合
在某些业务需求中不仅需要一个维度的桶聚合而且还可能有多维度桶嵌套聚合的需求。例如在搜索酒店时可能需要统计各个城市的满房和非满房状态下的酒店平均价格。ES支持嵌套桶聚合进行嵌套时可以使用aggs子句进行子桶的继续嵌套指标放在最里面的子桶内。以下DSL演示了多维度桶的使用方法
GET /hotel/_search
{ size: 0, aggs: { group_city: { //多维度桶名称 terms: { field: city }, aggs: { //单维度桶 group_full_room: { terms: { field: full_room }, aggs: { //聚合指标 my_sum: { avg: { field: price, missing: 200 } } } } } } }
} 上述DSL被执行后ES返回的结果如下
{ … hits : {…}, aggregations : { group_city : { //多维度聚合名称 doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //第一层桶聚合列表 { key : 北京, //第一层桶的key doc_count : 3, group_full_room : { //第二层桶聚合 doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //单维度聚合列表 { key : 1, key_as_string : true, doc_count : 2, my_sum : { //聚合指标 value : 378.0 } }, { key : 0, key_as_string : false, doc_count : 1, my_sum : { value : 200.0 } } ] } }, { key : 天津, doc_count : 2, group_full_room : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ { key : 0, key_as_string : false, doc_count : 1, my_sum : { value : 500.0 } }, { key : 1, key_as_string : true, doc_count : 1, my_sum : { value : 800.0 } } ] } } ] } }
} 结果中可以看到第一层的分桶先按照城市分组分为“北京”“天津”第二层在“北京”“天津”桶下面继续分桶分为“满房”“非满房”桶对应的聚合指标即价格的加和值存储在内部的my_sum字段中。
以下代码演示了在Java中使用多维度桶进行聚合的逻辑
public void getExternalBucketAggSearch () throws IOException{ //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String aggNameCitymy_terms_city; //按城市聚合的名称 //定义terms聚合指定字段为城市 TermsAggregationBuilder termsAggCity AggregationBuilders.terms
(aggNameCity).field(city); String aggNameFullRoommy_terms_full_room; //按满房状态聚合的名称 //定义terms聚合指定字段为满房状态 TermsAggregationBuilder termsArrFullRoom AggregationBuilders.terms
(aggNameCity).field(full_room); String sumAggNamemy_sum; //sum聚合的名称 //定义sum聚合指定字段为价格 SumAggregationBuilder sumAgg AggregationBuilders.sum(sumAggName).
field(price); //定义聚合的父子关系 termsArrFullRoom.subAggregation(sumAgg); termsAggCity.subAggregation(termsArrFullRoom); searchSourceBuilder.aggregation(termsAggCity); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 //执行查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Terms terms aggregations.get(aggNameCity); //获取聚合返回的对象 for(Terms.Bucket bucket:terms.getBuckets()){ //遍历第一层bucket //获取第一层bucket名称 String termsKeyCitybucket.getKey().toString(); System.out.println(--------termsKeyCitytermsKeyCity
--------); Terms termsFullRom bucket.getAggregations().get(aggNameCity); //遍历第二层bucket for(Terms.Bucket bucketFullRoom:termsFullRom.getBuckets()){ //获取第二层bucket名称 String termsKeyFullRoombucketFullRoom.getKeyAsString(); System.out.println(termsKeyFullRoomtermsKeyFullRoom); //获取聚合指标 Sum sum bucketFullRoom.getAggregations().get(sumAggName); String keysum.getName(); //获取聚合指标名称 double sumVal sum.getValue(); //获取聚合指标值 System.out.println(keykey,countsumVal); //打印结果 } }
} 4 聚合方式
ES支持灵活的聚合方式它不仅支持聚合和查询相结合而且还可以使聚合的过滤条件不影响搜索条件并且还支持在聚合后的结果中进行过滤筛选。本节将介绍这些聚合方式。
1 直接聚合
直接聚合指的是聚合时的DSL没有query子句是直接对索引内的所有文档进行聚合。前面介绍的示例都属于直接聚合这里不再进行演示。
2 先查询再聚合
与直接聚合相对应这种查询方式需要增加query子句query子句和普通的query查询没有区别参加聚合的文档必须匹配query查询。下面的DSL演示了这种用法
GET /hotel/_search
{ size: 0, query: { //指定查询query逻辑 term: { city: { value: 北京 } } }, aggs: { //指定聚合逻辑 my_agg: { avg: { field: price } } }
} 以下代码演示了在Java中先查询再聚合的逻辑
public void getQueryAggSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String avgAggName my_avg; //avg聚合的名称 //定义sum聚合指定字段为价格 AvgAggregationBuilder avgAgg AggregationBuilders.avg(avgAggName).
field(price); searchSourceBuilder.aggregation(avgAgg); //添加聚合 //构建query查询 searchSourceBuilder.query(QueryBuilders.termQuery(city, 北京)); searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 SearchHits searchHits searchResponse.getHits(); //获取搜索结果集 System.out.println(---------------hit--------------); for (SearchHit searchHit : searchHits) { //遍历搜索结果集 String index searchHit.getIndex(); //获取索引名称 String id searchHit.getId(); //获取文档_id Float score searchHit.getScore(); //获取得分 String source searchHit.getSourceAsString(); //获取文档内容 System.out.println(index index ,id id ,source
source); //打印数据 } System.out.println(---------------agg--------------); //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); ParsedAvg avg aggregations.get(avgAggName); //获取聚合返回的对象 String avgName avg.getName(); //获取聚合名称 double avgVal avg.getValue(); //获取聚合值 //打印结果 System.out.println(avgName avgName ,avgVal avgVal);
} 3 前过滤器
有时需要对聚合条件进一步地过滤但是又不能影响当前的查询条件。例如用户进行酒店搜索时的搜索条件是天津的酒店但是聚合时需要将非满房的酒店平均价格进行聚合并展示给用户。此时不能变更用户的查询条件需要在聚合子句中添加过滤条件。下面的DSL展示了在聚合时使用过滤条件的用法
GET /hotel/_search
{ size: 0, query: { //指定查询的query逻辑 term: { city: { value: 天津 } } }, aggs: { my_agg: { filter: { //指定过滤器逻辑 term: { full_room: false } }, aggs: { //指定聚合逻辑 my_avg: { avg: { field: price } } } } }
} 执行上述DSL后ES返回的结果如下
{ … hits : { total : { value : 2, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { doc_count : 1, //只有文档004没有被过滤 my_avg : { value : 500.0 } } }
} 通过上述结果可以知道满足查询条件的文档个数为2命中的文档为004和005但是在聚合时要求匹配非满房的酒店只有文档004满足聚合条件因此酒店的平均值为文档004的price字段值。
以下代码演示了在Java中使用前过滤器的逻辑
public void getFilterAggSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String filterAggName my_terms; //聚合的名称 TermQueryBuilder termQueryBuilderQueryBuilders.termQuery(full_room,
true); FilterAggregationBuilder filterAggregationBuilderAggregationBuilders.
filter(filterAggName,termQueryBuilder); String avgAggName my_avg; //avg聚合的名称 //定义聚合指定字段为价格 AvgAggregationBuilder avgAgg AggregationBuilders.avg(avgAggName).
field(price); //为filter聚合添加子聚合 filterAggregationBuilder.subAggregation(avgAgg); searchSourceBuilder.aggregation(filterAggregationBuilder); //添加聚合 //构建term查询 searchSourceBuilder.query(QueryBuilders.termQuery(city, 天津)); searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); //获取sum聚合返回的对象 ParsedFilter filter aggregations.get(filterAggName); Avg avg filter.getAggregations().get(avgAggName); String key avg.getName(); //获取聚合名称 double avgVal avg.getValue(); //获取聚合值 System.out.println(key key ,avgVal avgVal); //打印结果
} 4 后过滤器
在有些场景中需要根据条件进行数据查询但是聚合的结果集不受影响。例如在酒店搜索场景中用户的查询词为“假日”此时应该展现标题中带有“假日”的酒店。但是在该页面中如果还希望给用户呈现出全国各个城市的酒店的平均价格这时可以使用ES提供的后过滤器功能。该过滤器是在查询和聚合之后进行过滤的因此它的过滤条件对聚合没有影响。以下的DSL展示了后过滤器的使用
GET /hotel/_search
{ size: 0, query: { //指定查询的query逻辑 match: { title: 假日 } }, post_filter: { //指定后过滤器逻辑 term: { city: 北京 } }, aggs: { //指定聚合逻辑 my_agg: { avg: { field: price, missing:200 } } }
} 在上面的查询中使用match匹配title中包含“假日”的酒店并且查询出这些酒店的平均价格最后使用post_filter设置后过滤器的条件将酒店的城市锁定为“北京”执行该DSL后ES返回的结果如下
{ … hits : { total : { value : 3, relation : eq }, max_score : null, hits : [ ] }, aggregations : { my_agg : { //聚合时酒店的城市锁定为“北京” value : 364.0 } }
} 根据查询结果可知match查询命中了4个文档对这4个文档的price字段取平均值为364最后通过post_filter将其中的文档004过滤掉因此hits子句中的total数量为3。 以下代码演示了在Java中使用后过滤器的逻辑
public void getPostFilterAggSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String avgAggName my_avg; //avg聚合的名称 //定义sum聚合指定字段为价格 AvgAggregationBuilder avgAgg AggregationBuilders.avg(avgAggName).
field(price); avgAgg.missing(200); //设置默认值为200 searchSourceBuilder.aggregation(avgAgg); //添加聚合 //构建term查询 searchSourceBuilder.query(QueryBuilders.matchQuery(title, 假日)); TermQueryBuilder termQueryBuilderQueryBuilders.termQuery(city,北京); searchSourceBuilder.postFilter(termQueryBuilder); searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Avg avg aggregations.get(avgAggName); String key avg.getName(); //获取聚合名称 double avgVal avg.getValue(); //获取聚合值 System.out.println(key key ,avgVal avgVal); //打印结果
} 5 聚合排序
根据前面的介绍可知ES对于聚合结果的默认排序规则有时并非是我们期望的。可以使用ES提供的sort子句进行自定义排序有多种排序方式供用户选择可以按照聚合后的文档计数的大小进行排序可以按照聚合后的某个指标进行排序还可以按照每个组的名称进行排序。下面将介绍以上3种排序功能。
1 按文档计数排序
在聚合排序时业务需求可能有按照每个组聚合后的文档数量进行排序的场景。此时可以使用_count来引用每组聚合的文档计数进行排序。以下DSL演示了按照城市的酒店平均价格进行聚合并按照聚合后的文档计数进行升序排列的请求
GET /hotel/_search
{ size: 0, aggs: { group_city: { terms: { field: city, order: { //按照文档计数进行升序排列 _count: asc } }, aggs: { my_avg: { avg: { //使用价格平均值作为聚合指标 field: price, missing: 200 } } } } }
} 执行上述DSL后ES返回的结果如下
{ … hits : {…}, aggregations : { group_city : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //按照文档计数对桶聚合进行排序 { key : 天津, doc_count : 2, my_avg : { value : 650.0 } }, { key : 北京, doc_count : 3, my_avg : { value : 318.6666666666667 } } ] } }
} 以下代码演示了在Java中使用文档计数进行聚合排序的逻辑
public void getAggDocCountOrderSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String termsAggName my_terms; //聚合的名称 //定义terms聚合指定字段为城市 TermsAggregationBuilder termsAggregationBuilder AggregationBuilders.
terms(termsAggName).field(city); BucketOrder bucketOrderBucketOrder.count(true); termsAggregationBuilder.order(bucketOrder); String avgAggName my_avg; //avg聚合的名称 //定义sum聚合指定字段为价格 SumAggregationBuilder avgAgg AggregationBuilders.sum(avgAggName).
field(price); //定义聚合的父子关系 termsAggregationBuilder.subAggregation(avgAgg); searchSourceBuilder.aggregation(termsAggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 SearchHits searchHits searchResponse.getHits(); //获取搜索结果集 //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Terms terms aggregations.get(termsAggName); //获取聚合返回的对象 for (Terms.Bucket bucket : terms.getBuckets()) { String bucketKey bucket.getKey().toString(); System.out.println(termsKey bucketKey); Sum sum bucket.getAggregations().get(avgAggName); String key sum.getName(); //获取聚合名称 double sumVal sum.getValue(); //获取聚合值 System.out.println(key key ,count sumVal); //打印结果 }
} 2 按聚合指标排序
在聚合排序时业务需求可能有按照每个组聚合后的指标值进行排序的场景。此时可以使用指标的聚合名称来引用每组聚合的文档计数。以下DSL演示了按照城市的酒店平均价格进行聚合并按照聚合后的平均价格进行升序排列的请求
GET /hotel/_search
{ size: 0, aggs: { group_city: { terms: { field: city, order: { //按照聚合指标进行升序排列 my_avg: asc } }, aggs: { my_avg: { //定义聚合指标 avg: { field: price, missing: 200 } } } } }
} 执行上述DSL后ES返回的结果如下
… hits : {…}, aggregations : { group_city : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //按照价格平均值对桶聚合升序排列 { key : 北京, doc_count : 3, my_avg : { value : 318.6666666666667 } }, { key : 天津, doc_count : 2, my_avg : { value : 650.0 } } ] } }
} 以下代码演示了在Java中按照聚合指标进行聚合排序的逻辑
public void getAggMetricsOrderSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String termsAggName my_terms; //聚合的名称 //定义terms聚合指定字段为城市 String avgAggName my_avg; //avg聚合的名称 TermsAggregationBuilder termsAggregationBuilder AggregationBuilders.
terms(termsAggName).field(city); BucketOrder bucketOrderBucketOrder.aggregation(avgAggName,true); termsAggregationBuilder.order(bucketOrder); //定义sum聚合指定字段为价格 AvgAggregationBuilder avgAgg AggregationBuilders.avg(avgAggName).
field(price); //定义聚合的父子关系 termsAggregationBuilder.subAggregation(avgAgg); searchSourceBuilder.aggregation(termsAggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Terms terms aggregations.get(termsAggName); //获取聚合返回的对象 for (Terms.Bucket bucket : terms.getBuckets()) { String bucketKey bucket.getKey().toString(); System.out.println(termsKey bucketKey); Avg avg bucket.getAggregations().get(avgAggName); String key avg.getName(); //获取聚合名称 double avgVal avg.getValue(); //获取聚合值 System.out.println(key key ,avgVal avgVal);//打印结果 }
} 3按分组key排序
在聚合排序时业务需求可能有按照每个分组的组名称排序的场景。此时可以使用_key来引用分组名称。以下DSL演示了按照城市的酒店平均价格进行聚合并按照聚合后的分组名称进行升序排列的请求
GET /hotel/_search
{ size: 0, aggs: { group_city: { terms: { field: city, order: { //按照分组key的自然顺序升序排列 _key: asc } }, aggs: { my_avg: { //定义聚合指标 avg: { field: price, missing: 200 } } } } }
} 执行上述DSL后ES返回的结果如下
{ … hits : {…}, aggregations : { group_city : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //按照分组key的自然顺序对桶聚合升序排列 { key : 北京, doc_count : 3, my_avg : { value : 318.6666666666667 } }, { key : 天津, doc_count : 2, my_avg : { value : 650.0 } } ] } }
} 以下代码演示了在Java中按照分组key进行聚合排序的逻辑
public void getAggKeyOrderSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String termsAggName my_terms; //聚合的名称 //定义terms聚合指定字段为城市 String avgAggName my_avg; //avg聚合的名称 TermsAggregationBuilder termsAggregationBuilder AggregationBuilders.
terms(termsAggName).field(city); BucketOrder bucketOrderBucketOrder.key(true); termsAggregationBuilder.order(bucketOrder); //定义sum聚合指定字段为价格 SumAggregationBuilder avgAgg AggregationBuilders.sum(avgAggName).
field(price); //定义聚合的父子关系 termsAggregationBuilder.subAggregation(avgAgg); searchSourceBuilder.aggregation(termsAggregationBuilder); //添加聚合 searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 SearchHits searchHits searchResponse.getHits(); //获取搜索结果集 //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); Terms terms aggregations.get(termsAggName); //获取sum聚合返回的对象 for (Terms.Bucket bucket : terms.getBuckets()) { String bucketKey bucket.getKey().toString(); System.out.println(termsKey bucketKey); Sum sum bucket.getAggregations().get(avgAggName); String key sum.getName(); //获取聚合名称 double sumVal sum.getValue(); //获取聚合值 System.out.println(key key ,count sumVal);//打印结果 }
} 4 聚合分页
ES支持同时返回查询结果和聚合结果前面介绍聚合查询时查询结果和聚合结果各自封装在不同的子句中。但有时我们希望聚合的结果按照每组选出前N个文档的方式进行呈现最常见的一个场景就是电商搜索如搜索苹果手机6S搜索结果应该展示苹果手机6S型号中的一款手机即可而不论该型号手机的颜色有多少种。另外当聚合结果和查询结果封装在一起时还需要考虑对结果分页的问题此时前面介绍的聚合查询就不能解决这些问题了。ES提供的Top hits聚合和Collapse聚合可以满足上述需求但是这两种查询的分页方案是不同的。本节将介绍Top hits聚合和Collapse聚合并分别给出这两种查询的分页方案。
1 Top hits聚合
顾名思义Top hits聚合指的是聚合时在每个分组内部按照某个规则选出前N个文档进行展示。例如搜索“金都”时如果希望按照城市分组每组按照匹配分数降序展示3条文档数据DSL如下
GET /hotel/_search
{ size: 0, query: { match: { title: 金都 } }, aggs: { group_city: { //按照城市进行桶聚合 terms: { field: city }, aggs: { my_avg: { top_hits: { //指定返回每个桶的前3个文档 size: 3 } } } } }
} 执行上述查询后ES返回的结果如下
{ … hits : { … hits : [ ] }, aggregations : { group_city : { doc_count_error_upper_bound : 0, sum_other_doc_count : 0, buckets : [ //每个桶聚合中返回前3个文档 { key : 北京, doc_count : 2, my_avg : { hits : { … hits : [ { _index : hotel, _type : _doc, _id : 003, _score : 1.0928286, _source : { … } }, { _index : hotel, _type : _doc, _id : 002, _score : 0.96817136, _source : { … } ] } } }, { key : 天津, doc_count : 1, my_avg : { hits : { total : { value : 1, relation : eq }, max_score : 1.0928286, hits : [ { _index : hotel, _type : _doc, _id : 004, _score : 1.0928286, _source : { … } } ] } } } ] } }
} 可以看到在索引中一共有3个文档命中match查询条件在聚合结果中按照城市分成了两个组“北京”“天津”在“北京”下面有两个文档命中并且按照得分将展示文档进行了降序排列“天津”只有一个文档命中。
以下代码演示了在Java中使用Top hits聚合的逻辑
public void getAggTopHitsSearch() throws IOException { //创建搜索请求 SearchRequest searchRequest new SearchRequest(hotel); SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); String termsAggName my_terms; //聚合的名称 TermsAggregationBuilder termsAggregationBuilder AggregationBuilders.
terms(termsAggName).field(city); BucketOrder bucketOrderBucketOrder.key(true); termsAggregationBuilder.order(bucketOrder); String topHitsAggName my_top; //聚合的名称 TopHitsAggregationBuilder topHitsAggAggregationBuilders.topHits
(topHitsAggName); topHitsAgg.size(3); //定义聚合的父子关系 termsAggregationBuilder.subAggregation(topHitsAgg); //添加聚合 searchSourceBuilder.aggregation(termsAggregationBuilder); searchSourceBuilder.query(QueryBuilders.matchQuery(title,金都)); searchRequest.source(searchSourceBuilder); //设置查询请求 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 //获取聚合结果 Aggregations aggregations searchResponse.getAggregations(); //获取sum聚合返回的对象 Terms terms aggregations.get(termsAggName); for (Terms.Bucket bucket : terms.getBuckets()) { String bucketKey bucket.getKey().toString(); System.out.println(termsKey bucketKey); TopHits topHits bucket.getAggregations().get(topHitsAggName); SearchHit[] searchHitstopHits.getHits().getHits(); for(SearchHit searchHit:searchHits){ System.out.println(searchHit.getSourceAsString()); } } } Top hits聚合能满足“聚合的结果按照每组选出N个文档的方式进行呈现”的需求但是很遗憾它不能完成自动分页功能。如果在聚合中使用Top hits聚合并期望对数据进行分页则要求聚合的结果一定不能太多因为需要由客户端自行进行分页此时对分页内存的存储能力是一个挑战。可以一次性获取聚合结果并将其存放在内存中或者Redis中然后自行实现翻页逻辑完成翻页。假设数据一次性存储到Redis的list结构中以下示例代码演示了从Redis分页取数据的逻辑
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.List; Service
public class CacheServiceImpl { Resource private StringRedisTemplate stringRedisTemplate; //Redis客户端 public ListString getRecommendData(String key, Integer pageNo,
Integer pageSize) { long start (pageNo - 1) * pageSize; //计算分页的起始位置 long end pageNo * pageSize - 1; //计算分页的终止位置 //从Redis中取数据并将数据返回 return stringRedisTemplate.opsForList().range(key, start, end); }
}
2 Collapse聚合
如前面所述当在索引中有大量数据命中时Top hits聚合存在效率问题并且需要用户自行排序。针对上述问题ES推出了Collapse聚合即用户可以在collapse子句中指定分组字段匹配query的结果按照该字段进行分组并在每个分组中按照得分高低展示组内的文档。当用户在query子句外指定from和size时将作用在Collapse聚合之后即此时的分页是作用在分组之后的。以下DSL展示了Collapse聚合的用法
GET /hotel/_search
{ from: 0, //指定分页的起始位置 size: 5, //指定每页返回的数量 query: { //指定查询的query逻辑 match: { title: 金都 } }, collapse: { //指定按照城市进行Collapse聚合 field: city }
} 执行上述DSL后ES返回的结果如下
{ … hits : { … hits : [ { … _id : 003, _score : 1.0928286, _source : { … }, fields : { //按照城市进行Collapse聚合 city : [ 北京 ] } }, { … _id : 004, _score : 1.0928286, _source : { … }, fields : { //按照城市进行Collapse聚合 city : [ 天津 ] } } ] }
} 从结果中可以看到与Top hits聚合不同Collapse聚合的结果是封装在hit中的。在索引中一共有3个文档命中match查询条件在聚合结果中已经按照城市分成了两个组即“北京”“天津”在“北京”下面有两个文档命中其中得分最高的文档为003“天津”只有一个文档命中。上述结果不仅能按照得分进行排序并且具备分页功能。 以下代码演示了在Java中使用Collapse聚合的逻辑
public void getCollapseAggSearch() throws IOException{ //按照spu进行分组 //按照城市进行分组 CollapseBuilder collapseBuilder new CollapseBuilder(city); SearchRequest searchRequest new SearchRequest(); //新建搜索请求 SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder(); //新建match查询 searchSourceBuilder.query(QueryBuilders.matchQuery(title, 金都)); searchSourceBuilder.collapse(collapseBuilder); //设置Collapse聚合 searchRequest.source(searchSourceBuilder); //设置查询 SearchResponse searchResponse client.search(searchRequest,
RequestOptions.DEFAULT); //执行搜索 SearchHits searchHits searchResponse.getHits(); //获取搜索结果集 for (SearchHit searchHit : searchHits) { //遍历搜索结果集 String index searchHit.getIndex(); //获取索引名称 String id searchHit.getId(); //获取文档_id Float score searchHit.getScore(); //获取得分 String source searchHit.getSourceAsString(); //获取文档内容 System.out.println(index index ,id id ,score score ,source source); //打印数据 }
}