Elasticsearch強大的聚合功能Facet


在常規數據庫中,我們都知道有一個sql就是group,分組。如果主表只有對應的一個列記錄的分組的ID,那么還好統計,比如說每本書book表,有一個分類catId,記錄是屬於哪一類的書,那么直接按照catId進行分組即可。可是在實際應用種,並非如此簡單。一本書往往屬於多個分類,比如:某本書既屬於科技類書,又屬於兒童類書,要求按照這兩種條件進行篩選,都能篩選出來,如果要求按照分類進行統計數量,數據庫怎么group?我們且拋開種種解決方案,來看看Elasticsearch里面對這種需求,是多么的容易統計。

 

首先,我們需要造些數據,需要用到一個模型,這個模型定義了一個type,就算類型吧,我們用這個屬性來演示常規的group。還有一個catIds的列表模型,這個來解決我們上面描述的一本書對應多個分類的需求。模型定義如下:

import java.io.Serializable;  
import java.util.ArrayList;  
import java.util.List;  
import java.util.Random;  
  
import com.donlianli.es.ESUtils;  
/** 
 * 這個是為分組定義的一個模型 
 * catIds通常為一對多的分類ID 
 * @author donlian 
 */  
public class FacetTestModel implements Serializable {  
    private static final long serialVersionUID = 3174577828007649745L;  
    /** 
     * 隨便編寫的一些值,type屬性只能取這里面的其中一個 
     */  
    private String[] types= new String[]{  
            "type1","type2","type3","type4","type5","type6","type7",  
            "type11","type12","type13","type14","type15","type16","type17"  
    };  
    //主ID  
    private long id;  
    //類型,為types之一  
    private String type;  
    /** 
     * 所屬分類,范圍為1-50 
     */  
    private List<Integer> catIds;  
      
    public FacetTestModel(){  
        Random r = new Random();  
        int n = Math.abs(r.nextInt());  
        int index = n%14;  
        this.type = types[index];  
        this.id = Math.abs(r.nextLong());  
          
        n = n%50;  
        catIds = new ArrayList<Integer>();  
        catIds.add(n);  
        int ys = n%3;  
        if(ys!=0){  
            for(int i=1;i<ys+1;i++){  
                catIds.add(n+i);  
            }  
        }  
    }  
    public static void main(String[] argv){  
        for(int i=0;i<10;i++){  
            FacetTestModel f = new FacetTestModel();  
            System.out.println(ESUtils.toJson(f));  
        }  
    }  
    set,get方法,自己寫吧
}  

 接着就是初始化數據。

import org.elasticsearch.action.bulk.BulkRequestBuilder;  
import org.elasticsearch.action.bulk.BulkResponse;  
import org.elasticsearch.action.index.IndexRequestBuilder;  
import org.elasticsearch.client.Client;  
  
import com.donlianli.es.ESUtils;  
import com.donlianli.es.model.FacetTestModel;  
  
public class BulkIndexTest {  
      
    public static void main(String[] args) {  
        Client client = ESUtils.getClient();  
        BulkRequestBuilder bulkRequest = client.prepareBulk();  
        for(int i=0;i<10;i++){  
            String json = ESUtils.toJson(new FacetTestModel());  
            IndexRequestBuilder indexRequest = client.prepareIndex("test", "test")  
            //指定不重復的ID        
            .setSource(json).setId(String.valueOf(i));  
            //添加到builder中  
            bulkRequest.add(indexRequest);  
        }  
          
        BulkResponse bulkResponse = bulkRequest.execute().actionGet();  
        if (bulkResponse.hasFailures()) {  
            System.out.println(bulkResponse.buildFailureMessage());  
        }  
    }  
}  

接下來,我們首先對type進行統計。在elasticsearch中,分組的功能叫facet,不知道為啥起這個名稱。總之,就是對type的每一個值的數量進行統計,注意,要設置里面的size條件,否則默認只返回10個。

import org.elasticsearch.action.search.SearchResponse;  
import org.elasticsearch.client.Client;  
import org.elasticsearch.index.query.FilterBuilders;  
import org.elasticsearch.search.facet.FacetBuilders;  
import org.elasticsearch.search.facet.Facets;  
import org.elasticsearch.search.facet.terms.TermsFacet;  
import org.elasticsearch.search.facet.terms.TermsFacetBuilder;  
  
import com.donlianli.es.ESUtils;  
  
public class GroupTest {  
    public static void  main(String[] argv){  
        Client client = ESUtils.getClient();  
        TermsFacetBuilder facetBuilder = FacetBuilders.termsFacet("typeFacetName");  
        facetBuilder.field("type").size(Integer.MAX_VALUE);  
        facetBuilder.facetFilter(FilterBuilders.matchAllFilter());  
        SearchResponse response = client.prepareSearch("test")  
                .setTypes("test")  
                .addFacet(facetBuilder)  
                .setFilter(FilterBuilders.matchAllFilter())  
                .execute()  
                .actionGet();  
        Facets f = response.facets();  
        //跟上面的名稱一樣  
        TermsFacet facet = (TermsFacet)f.getFacets().get("typeFacetName");  
        for(TermsFacet.Entry tf :facet.entries()){  
            System.out.println(tf.getTerm()+"\t:\t" + tf.getCount());  
        }  
        client.close();  
    }  
}  

運行程序后,大概得到如下結果:

type3   :   4  
type7   :   1  
type6   :   1  
type4   :   1  
type13  :   1  
type12  :   1  
type11  :   1  

正好10個。初始化代碼能對的上。

下面,我們就要對catIds進行統計了,再統計之前,我們先看看es里面都存儲的是那些數據。

{id=3683174899323317453, catIds=[4, 5], type=type3}  
{id=271209313870366004, catIds=[26, 27, 28], type=type3}  
{id=348654892174153835, catIds=[41, 42, 43], type=type4}  
{id=6826187683023110944, catIds=[46, 47], type=type7}  
{id=3437591661789488747, catIds=[22, 23], type=type3}  
{id=6365837443081614150, catIds=[37, 38], type=type11}  
{id=2387331048448677498, catIds=[20, 21, 22], type=type3}  
{id=5595404824923951817, catIds=[31, 32], type=type13}  
{id=3593797446463621044, catIds=[30], type=type12}  
{id=5824112111832084165, catIds=[1, 2], type=type6}  

怎么對catIds進行統計呢,代碼跟上面進行單個統計一樣。

import org.elasticsearch.action.search.SearchResponse;  
import org.elasticsearch.client.Client;  
import org.elasticsearch.index.query.FilterBuilders;  
import org.elasticsearch.search.facet.FacetBuilders;  
import org.elasticsearch.search.facet.Facets;  
import org.elasticsearch.search.facet.terms.TermsFacet;  
import org.elasticsearch.search.facet.terms.TermsFacetBuilder;  
  
import com.donlianli.es.ESUtils;  
  
public class GroupTest2 {  
    public static void  main(String[] argv){  
        Client client = ESUtils.getClient();  
        TermsFacetBuilder facetBuilder = FacetBuilders.termsFacet("catIdName");  
        facetBuilder.field("catIds").size(Integer.MAX_VALUE);  
        facetBuilder.facetFilter(FilterBuilders.matchAllFilter());  
        SearchResponse response = client.prepareSearch("test")  
                .setTypes("test")  
                .addFacet(facetBuilder)  
                .setFilter(FilterBuilders.matchAllFilter())  
                .execute()  
                .actionGet();  
        Facets f = response.facets();  
        //跟上面的名稱一樣  
        TermsFacet facet = (TermsFacet)f.getFacets().get("catIdName");  
        for(TermsFacet.Entry tf :facet.entries()){  
            System.out.println("鍵:"+tf.getTerm()+"\t;數量:\t" + tf.getCount());  
        }  
        client.close();  
    }  
}  

運行結果:

鍵:22    ;數量:    2  
鍵:47    ;數量:    1  
鍵:46    ;數量:    1  
鍵:43    ;數量:    1  
鍵:42    ;數量:    1  
鍵:41    ;數量:    1  
鍵:38    ;數量:    1  
鍵:37    ;數量:    1  
鍵:32    ;數量:    1  
鍵:31    ;數量:    1  
鍵:30    ;數量:    1  
鍵:28    ;數量:    1  
鍵:27    ;數量:    1  
鍵:26    ;數量:    1  
鍵:23    ;數量:    1  
鍵:21    ;數量:    1  
鍵:20    ;數量:    1  
鍵:5 ;數量:    1  
鍵:4 ;數量:    1  
鍵:2 ;數量:    1  
鍵:1 ;數量:    1  

再和上面的數據對對,是不是除了22,其他的都是一個?

 

在分組這方面,ES真的很強大,除了上面的支持列表分組外,還支持范圍分組rangeFacet,多個分組可以一次全部發送給ES等等,更多功能,大家還是自己多多驗證。

 

 

對這類話題感興趣?歡迎發送郵件至 donlianli@126.com;或者關注我的微信公眾號“猿界汪汪隊”
關於我:邯鄲人,擅長Java,Javascript,Extjs,oracle sql。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM