1、Elasticsearch的聚合
ES的聚合至關於關係型數據庫裏面的group by,例如查找在性別字段男女人數的多少而且按照人數的多少進行排序,在使用MySQL的時候,可使用以下的句子html
- select sex,count(*) from table_name group by sex order by count(*)
在ES裏面想要實現這種的語句,就叫作聚合,好比這種的聚合使用DSL語句的話以下所示:java
- GET /index/type/_search
- {
- "size" : 0,
- "aggs" : {
- "agg_sex" : {
- "terms" : {
- "field" : "sex"
- }
- }
- }
- }
這樣就能夠實現最以上例子中的group by的功能,固然這只是最簡單的聚合的使用,在ES裏面的聚合有多重多樣的,好比說有度量聚合,能夠用來計算某一個字段的平均值最大值等,在此給出一個簡單的度量聚合的例子數據庫
- GET /index/type/_search
- {
- "size" : 0,
- "aggs": {
- "agg_sex": {
- "terms": {
- "field": "sex"
- },
- "agg_age": {
- "avg_age": {
- "avg": {
- "field": "age"
- }
- }
- }
- }
- }
- }
這個DSL語句就是將先按照性別進行聚合,而且對不一樣的性別給出一個平均的年齡,使用以後ES的給出結果以下所示:api
- {
- ...
- "aggregations": {
- "agg_sex": {
- "buckets": [
- {
- "key": "male",
- "doc_count": 4,
- "avg_age": {
- "value": 25
- }
- },
- {
- "key": "female",
- "doc_count": 2,
- "avg_age": {
- "value": 23
- }
- }
- ]
- }
- }
- ...
- }
在度量聚合裏面有min,max,sum,avg聚合等,還有stats,extern_stats等聚合,其中stats的聚合給出的信息會包括min,max,count等基本的信息,更多詳細的細節請參考ES官網給出的指導https://www.elastic.co/guide/en/elasticsearch/guide/current/aggregations.htmlelasticsearch
以上只是給出的度量聚合,可是在實際中咱們常用的是桶聚合,什麼是桶聚合呢,我的理解就是將符合某一類條件的文檔選出來,全部的某一類的聚合就稱爲桶,例如你能夠按照某一個分類將全部的商品聚合起來,這種狀況下就能夠認爲某一個分類的商品稱爲一個桶,下面將詳細介紹幾個經常使用的桶聚合,而且會給出Java使用時候的代碼ide
2、桶聚合
桶聚合是在實際使用時候用處比較多的一種聚合,簡單的桶聚合包括term聚合,range聚合,date聚合,IPV4聚合等聚合,由於本身使用的僅僅是其中的三個,在此就簡單的介紹三個,分別是term聚合,range聚合,以及date聚合函數
一、term聚合
term聚合就是第一部分給出的簡單的例子,按照不一樣的字段進行聚合post
二、range聚合
range聚合爲按照自定義的範圍來創造桶,將每個範圍的數據進行聚合,而且這個聚合通常適用於字段類型爲long或者int,double的字段,能夠進行直接的聚合,例如,咱們想統計不一樣年齡段的人的個數,DSL以下所示:學習
- GET /index/type/_search
- {
- "aggs" : {
- "agg_age" : {
- "field":"age"
- "ranges" : [
- { "to" : 18},
- { "from" : 19,"to" : 50},
- {"from" : 51}
- ]
- }
- }
- }
三、daterange聚合
date range聚合和range聚合相似,可是所使用的類型是datetime這種類型,使用的時候與range有些區別,給出一個簡單的使用date range聚合的DSL例子,以下所示:ui
- GET /index/type/_search
- {
- "aggs" : {
- "agg_year" : {
- "field":"date"
- "ranges" : [
- { "to" : "2008-08-08"},
- { "from" : "2008-08-09","to" : "2012-09-01"},
- {"from" : "2012-09-02"}
- ]
- }
- }
- }
上面的DSL是簡單的按照時間格式進行區間的聚合,可是有些時候咱們可能想要一些按照年份聚合或者月份聚合的狀況,這個時候應該怎麼辦呢?在date range裏面能夠指定日期的格式,例以下面給出一個按照年份進行聚合的例子:
- GET /index/type/_search
- {
- "aggs" : {
- "agg_year" : {
- "field":"date"
- "format":"YYYY",
- "ranges" : [
- { "to" : "1970"},
- { "from" : "1971","to" : "2012"},
- {"from" : "2013"}
- ]
- }
- }
- }
咱們能夠指定格式來進行聚合
3、對於上述三種聚合java的實現
首先先給出一個具體的使用ES java api實現搜索而且聚合的完整例子,例子中使用的是terms聚合,按照分類id,將全部的分類進行聚合
- public void aggsearch() {
- init();
- SearchResponse response = null;
-
- SearchRequestBuilder responsebuilder = client.prepareSearch("iktest")
- .setTypes("iktest").setFrom(0).setSize(250);
- AggregationBuilder aggregation = AggregationBuilders
- .terms("agg")
- .field("category_id")
- .subAggregation(
- AggregationBuilders.topHits("top").setFrom(0)
- .setSize(10)).size(100);
- response = responsebuilder.setQuery(QueryBuilders.boolQuery()
-
- .must(QueryBuilders.matchPhraseQuery("name", "中學歷史")))
- .addSort("category_id", SortOrder.ASC)
- .addAggregation(aggregation)// .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
- .setExplain(true).execute().actionGet();
-
- SearchHits hits = response.getHits();
-
- Terms agg = response.getAggregations().get("agg");
- System.out.println(agg.getBuckets().size());
- for (Terms.Bucket entry : agg.getBuckets()) {
- String key = (String) entry.getKey(); // bucket key
- long docCount = entry.getDocCount(); // Doc count
- System.out.println("key " + key + " doc_count " + docCount);
-
- // We ask for top_hits for each bucket
- TopHits topHits = entry.getAggregations().get("top");
- for (SearchHit hit : topHits.getHits().getHits()) {
- System.out.println(" -> id " + hit.getId() + " _source [{}]"
- + hit.getSource().get("category_name"));
- ;
- }
- }
- System.out.println(hits.getTotalHits());
- int temp = 0;
- for (int i = 0; i < hits.getHits().length; i++) {
- // System.out.println(hits.getHits()[i].getSourceAsString());
- System.out.print(hits.getHits()[i].getSource().get("product_id"));
- // if(orderfield!=null&&(!orderfield.isEmpty()))
- // System.out.print("\t"+hits.getHits()[i].getSource().get(orderfield));
- System.out.print("\t"
- + hits.getHits()[i].getSource().get("category_id"));
- System.out.print("\t"
- + hits.getHits()[i].getSource().get("category_name"));
- System.out.println("\t"
- + hits.getHits()[i].getSource().get("name"));
- }
- }
- }
以上的例子實現的是按照category_id字段進行分類的聚合,而且將在name字段查找包含「中學歷史」的這個詞,而且按照category_id進行排序,在此給出的只是一個搜索實現的函數,裏面的字段名字,以及index,type等不少字段均爲本身定義的index裏面的名字,上面給出的是terms聚合時候的代碼,若是使用的是range聚合或者date range聚合,只須要改變aggregation就能夠
使用range聚合的時候:
- aggregation = AggregationBuilders.range("agg")
- .field("price").addUnboundedTo(50)
- .addRange(51, 100).addRange(101, 1000)
- .addUnboundedFrom(1001);
使用date range聚合的時候:
- aggregation = AggregationBuilders.dateRange("agg")
- .field("date").format("yyyy")
- .addUnboundedTo("1970").addRange("1970", "2000")
- .addRange("2000", "2010").addUnboundedFrom("2009");
以上全部的聚合均是先過濾搜索,而後對於召回獲得的結果進行一個聚合,例如咱們在name字段搜索中學歷史這個詞,最終獲得四個分類分別爲1,2,3,4那麼聚合的時候就是這四個分類,可是有時候咱們可能會須要對於搜索的結果進行一個過濾,可是咱們不想對聚合的結果進行過濾,那麼咱們就要使用一下的部分了
4、先聚合再過濾
以上將的簡單的聚合都是先過濾或者搜索,而後對結果進行聚合,可是有時候咱們須要先進行聚合,而後再對結果進行一次過濾,可是咱們不但願這個時候聚合會發生變化,何時會遇到這種狀況呢,咱們以美團爲例作一個說明,在主頁咱們直接點解美食,獲得以下所示的圖
點美食以後出現所有的分類,包括各類的菜系,下面咱們點一個具體的菜系
從程序上來講,咱們點第二次菜系的時候,出現的全部的菜品均是烤串之類的菜品了,可是在分類裏面仍是全部的分類都會有,若是按照以前的ES的聚合,會將全部搜索出來的品的分類進行一個聚合,可是點完烤串以後,全部的分類都是烤串了,那麼就應該全部的分類只有一個烤串了,不該該有其餘的,這樣的話確定是不能夠的,那麼如何才能實現這種聚合的,這個時候咱們就須要先聚合,而後進行再次的過濾,可是過濾的時候並不影響以前的聚合結果,這就是先聚合再過濾,在ES裏面也有這種狀況的考慮,這個時候使用的是postfilter
postfilter解決了僅僅過濾搜索結果,可是並不影響聚合結果,下面給出一個java使用時候的例子以及比較
函數一爲第三部分給出的完整的搜索函數,按照分類聚合
函數二的改變只是對於一的
- response = responsebuilder.setQuery(QueryBuilders.boolQuery()
-
- .must(QueryBuilders.matchPhraseQuery("name", "中學歷史")))
- .addSort("category_id", SortOrder.ASC)
- .addAggregation(aggregation)
- .setPostFilter(QueryBuilders.rangeQuery("price").gt(1000).lt(5000))
- .setExplain(true).execute().actionGet();
添加了按照price進行過濾,最後結果顯示,聚合的結果兩次徹底同樣,可是函數二召回的結果爲函數一結果的子集。
5、後續學習
如何屢次的過濾以及召回,好比先過濾後聚合再過濾再次聚合而後再次過濾這種的應該如何實現,須要學習。