Elasticsearch强大的聚合功能Facet

2019-03-11 09:20|来源: 网络

在常规数据库中,我们都知道有一个sql就是group,分组。如果主表只有对应的一个列记录的分组的ID,那么还好统计,比如说每本书book表,有一个分类catId,记录是属于哪一类的书,那么直接按照catId进行分组即可。可是在实际应用种,并非如此简单。一本书往往属于多个分类,比如:某本书既属于科技类书,又属于儿童类书,要求按照这两种条件进行筛选,都能筛选出来,如果要求按照分类进行统计数量,数据库怎么group?我们且抛开种种解决方案,来看看Elasticsearch里面对这种需求,是多么的容易统计。

首先,我们需要造些数据,需要用到一个模型,这个模型定义了一个type,就算类型吧,我们用这个属性来演示常规的group。还有一个catIds的列表模型,这个来解决我们上面描述的一本书对应多个分类的需求。模型定义如下:

Java代码
  1. import java.io.Serializable;  

  2. import java.util.ArrayList;  

  3. import java.util.List;  

  4. import java.util.Random;  

  5.  

  6. import com.donlianli.es.ESUtils;  

  7. /**

  8. * 这个是为分组定义的一个模型

  9. * catIds通常为一对多的分类ID

  10. * @author donlian

  11. */  

  12. public class FacetTestModel implements Serializable {  

  13.    private static final long serialVersionUID = 3174577828007649745L;  

  14.    /**

  15.     * 随便编写的一些值,type属性只能取这里面的其中一个

  16.     */  

  17.    private String[] types= new String[]{  

  18.            "type1","type2","type3","type4","type5","type6","type7",  

  19.            "type11","type12","type13","type14","type15","type16","type17"  

  20.    };  

  21.    //主ID  

  22.    private long id;  

  23.    //类型,为types之一  

  24.    private String type;  

  25.    /**

  26.     * 所属分类,范围为1-50

  27.     */  

  28.    private List<Integer> catIds;  

  29.      

  30.    public FacetTestModel(){  

  31.        Random r = new Random();  

  32.        int n = Math.abs(r.nextInt());  

  33.        int index = n%14;  

  34.        this.type = types[index];  

  35.        this.id = Math.abs(r.nextLong());  

  36.          

  37.        n = n%50;  

  38.        catIds = new ArrayList<Integer>();  

  39.        catIds.add(n);  

  40.        int ys = n%3;  

  41.        if(ys!=0){  

  42.            for(int i=1;i<ys+1;i++){  

  43.                catIds.add(n+i);  

  44.            }  

  45.        }  

  46.    }  

  47.    public static void main(String[] argv){  

  48.        for(int i=0;i<10;i++){  

  49.            FacetTestModel f = new FacetTestModel();  

  50.            System.out.println(ESUtils.toJson(f));  

  51.        }  

  52.    }  

  53.    public long getId() {  

  54.        return id;  

  55.    }  

  56.    public void setId(long id) {  

  57.        this.id = id;  

  58.    }  

  59.    public String getType() {  

  60.        return type;  

  61.    }  

  62.    public void setType(String type) {  

  63.        this.type = type;  

  64.    }  

  65.    public List<Integer> getCatIds() {  

  66.        return catIds;  

  67.    }  

  68.    public void setCatIds(List<Integer> catIds) {  

  69.        this.catIds = catIds;  

  70.    }  

  71. }  

接着就是初始化数据。

Java代码
  1. import org.elasticsearch.action.bulk.BulkRequestBuilder;  

  2. import org.elasticsearch.action.bulk.BulkResponse;  

  3. import org.elasticsearch.action.index.IndexRequestBuilder;  

  4. import org.elasticsearch.client.Client;  

  5.  

  6. import com.donlianli.es.ESUtils;  

  7. import com.donlianli.es.model.FacetTestModel;  

  8.  

  9. public class BulkIndexTest {  

  10.      

  11.    public static void main(String[] args) {  

  12.        Client client = ESUtils.getClient();  

  13.        BulkRequestBuilder bulkRequest = client.prepareBulk();  

  14.        for(int i=0;i<10;i++){  

  15.            String json = ESUtils.toJson(new FacetTestModel());  

  16.            IndexRequestBuilder indexRequest = client.prepareIndex("test", "test")  

  17.            //指定不重复的ID        

  18.            .setSource(json).setId(String.valueOf(i));  

  19.            //添加到builder中  

  20.            bulkRequest.add(indexRequest);  

  21.        }  

  22.          

  23.        BulkResponse bulkResponse = bulkRequest.execute().actionGet();  

  24.        if (bulkResponse.hasFailures()) {  

  25.            System.out.println(bulkResponse.buildFailureMessage());  

  26.        }  

  27.    }  

  28. }  

接下来,我们首先对type进行统计。在elasticsearch中,分组的功能叫facet,不知道为啥起这个名称。总之,就是对type的每一个值的数量进行统计,注意,要设置里面的size条件,否则默认只返回10个。

Java代码
  1. import org.elasticsearch.action.search.SearchResponse;  

  2. import org.elasticsearch.client.Client;  

  3. import org.elasticsearch.index.query.FilterBuilders;  

  4. import org.elasticsearch.search.facet.FacetBuilders;  

  5. import org.elasticsearch.search.facet.Facets;  

  6. import org.elasticsearch.search.facet.terms.TermsFacet;  

  7. import org.elasticsearch.search.facet.terms.TermsFacetBuilder;  

  8.  

  9. import com.donlianli.es.ESUtils;  

  10.  

  11. public class GroupTest {  

  12.    public static void  main(String[] argv){  

  13.        Client client = ESUtils.getClient();  

  14.        TermsFacetBuilder facetBuilder = FacetBuilders.termsFacet("typeFacetName");  

  15.        facetBuilder.field("type").size(Integer.MAX_VALUE);  

  16.        facetBuilder.facetFilter(FilterBuilders.matchAllFilter());  

  17.        SearchResponse response = client.prepareSearch("test")  

  18.                .setTypes("test")  

  19.                .addFacet(facetBuilder)  

  20.                .setFilter(FilterBuilders.matchAllFilter())  

  21.                .execute()  

  22.                .actionGet();  

  23.        Facets f = response.getFacets();  

  24.        //跟上面的名称一样  

  25.        TermsFacet facet = (TermsFacet)f.getFacets().get("typeFacetName");  

  26.        for(TermsFacet.Entry tf :facet.getEntries()){  

  27.            System.out.println(tf.getTerm()+"\t:\t" + tf.getCount());  

  28.        }  

  29.        client.close();  

  30.    }  

  31. }  


运行程序后,大概得到如下结果:

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进行统计呢,代码跟上面进行单个统计一样。

Java代码
  1. import org.elasticsearch.action.search.SearchResponse;  

  2. import org.elasticsearch.client.Client;  

  3. import org.elasticsearch.index.query.FilterBuilders;  

  4. import org.elasticsearch.search.facet.FacetBuilders;  

  5. import org.elasticsearch.search.facet.Facets;  

  6. import org.elasticsearch.search.facet.terms.TermsFacet;  

  7. import org.elasticsearch.search.facet.terms.TermsFacetBuilder;  

  8.  

  9. import com.donlianli.es.ESUtils;  

  10.  

  11. public class GroupTest2 {  

  12.    public static void  main(String[] argv){  

  13.        Client client = ESUtils.getClient();  

  14.        TermsFacetBuilder facetBuilder = FacetBuilders.termsFacet("catIdName");  

  15.        facetBuilder.field("catIds").size(Integer.MAX_VALUE);  

  16.        facetBuilder.facetFilter(FilterBuilders.matchAllFilter());  

  17.        SearchResponse response = client.prepareSearch("test")  

  18.                .setTypes("test")  

  19.                .addFacet(facetBuilder)  

  20.                .setFilter(FilterBuilders.matchAllFilter())  

  21.                .execute()  

  22.                .actionGet();  

  23.        Facets f = response.facets();  

  24.        //跟上面的名称一样  

  25.        TermsFacet facet = (TermsFacet)f.getFacets().get("catIdName");  

  26.        for(TermsFacet.Entry tf :facet.entries()){  

  27.            System.out.println("键:"+tf.getTerm()+"\t;数量:\t" + tf.getCount());  

  28.        }  

  29.        client.close();  

  30.    }  

  31. }  


运行结果:

键: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。    
更多我之前的文章,可以访问  我的空间    

 
转自:http://donlianli.iteye.com/blog/1906747

相关问答

更多
  • 使用elasticsearch的facet统计功能实现统计 1,节省机器及运维成本:数据仍给opensearch,访问突增、数据突增系统上修改下配额,其他一概不关心。神马宕机、分布式、双集群、扩容、网络异常统统与我无关; 2,收录数据自由控制:想收录什么内容就收录什么内容,完全自由控制,分钟级生效(很快到秒级); 3,阿里云数据自动对接:OSS已经上线,ODPS\RDS稳步开发中,其他产品陆续都会支持。opensearch自动获取数据,一份数据多个场景使用,别提多省心了; 4,应用结构自定义:搜索schem ...
  • 这里是正确的聚合查询(提示:你需要使用reverse_nested跳出嵌套上下文到父节点) { "aggs": { "members": { "nested": { "path": "members" }, "aggs": { "language": { "terms": { "field": "members.language" }, "agg ...
  • 如果我理解得很好,您似乎需要计算所有类别的聚合,然后过滤它以一次只显示一个类别。 这可以使用post_filter实现(参见文档 )。 它是在计算聚合之后应用的过滤器。 在你的情况下,而不是像这样查询: { "query": { "bool": { "must": [ {"term": {"color": "red"} }, {"term": {"category": "casual"}} .... ] } }, ...
  • 您可以为此使用filters aggregation 。 请注意附加的s ,这与您已经提到的filter聚合不同。 { "query": { "match_all": {} }, "size": 0, "aggs": { "values": { "filters": { "filters": { "value1": { "bool": { "should": [ ...
  • 编辑错误的答案。 我因为背景离开了。 澄清: and是一个合适的过滤器,应该被facet_filter接受。 不确定是什么。 未经测试,但来自文档:( http://www.elasticsearch.org/guide/reference/api/search/facets/ ) 可以使用其他过滤器配置所有方面(在“查询DSL”部分中进行了说明) 因此,您需要在facet_filter中facet_filter适当的query 。 并不是一个合适的过滤器(你收到的错误可能更清楚) 例如: "facet_f ...
  • 您应该尝试使用$facet aggregation来获得期望的结果,这个结果非常易于使用limit和skip ... 在这里检查输出 db.collection.aggregate([ { "$facet": { "top": [ { "$group": { "_id": "$Category", "response": { "$sum": "$response" } }}, { "$sort ...
  • 我通过修改脚本并从agg中删除“field”找到了解决方法: { "aggs" : { "agg_name" : { "terms" : { "script" : "if (_source.location == \"UA\") {return _source.location} else {return null}" } } } } I've found workaround ...
  • 如果您了解SQL,则elasticsearch聚合是elasticsearch的分组子句。 你可以在你想要的字段上聚合(group by),可以在该字段上有文件计数,也可以有该组中的所有文件,可以嵌套aggs(group by)。 对于建议的搜索条件,当你键入 aggs时将不起作用..你需要阅读关于分析文档......或者阅读elasticsearch中的模糊查询。 If you know SQL, elasticsearch aggregations are kind of group by clause ...
  • allocated的变量总是等于count (因此可以删除),而IMO可能会混淆使用'0'和'1'作为allocated_timers数组中的值。 通常它会是0和1 。 它们都不会影响代码的健壮性,但是代码越容易理解,它对未来的修改就越强大。 如果你有两个“并行”数组,就像你在这里做的那样每个计时器都有一个定时器中的条目和allocated_timers的相应条目,值得考虑是否有一个带有两个成员的struct的单个数组(在这种情况可能是命名value和allocated )。 有时它并不好,但通常它有助于理 ...