zoukankan      html  css  js  c++  java
  • ElasticSearch的高级复杂查询:非聚合查询和聚合查询

    一、非聚合复杂查询(这儿展示了非聚合复杂查询的常用流程)

    1. 查询条件QueryBuilder的构建方法
    • 1.1 精确查询(必须完全匹配上,相当于SQL语句中的“=”)

    ① 单个匹配 termQuery
    //不分词查询 参数1: 字段名,参数2:字段查询值,因为不分词,所以汉字只能查询一个字,英语是一个单词.
    QueryBuilder queryBuilder=QueryBuilders.termQuery("fieldName", "fieldlValue");
    //分词查询,采用默认的分词器
    QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("fieldName", "fieldlValue");


    ② 多个匹配
    //不分词查询,参数1: 字段名,参数2:多个字段查询值,因为不分词,所以汉字只能查询一个字,英语是一个单词.
    QueryBuilder queryBuilder=QueryBuilders.termsQuery("fieldName", "fieldlValue1","fieldlValue2...");
    //分词查询,采用默认的分词器
    QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("fieldlValue", "fieldName1", "fieldName2", "fieldName3");
    //匹配所有文件,相当于就没有设置查询条件
    QueryBuilder queryBuilder=QueryBuilders.matchAllQuery();

     

    • 1.2 模糊查询(只要包含即可,相当于SQL语句中的“LIKE”)

    ① 常用的字符串查询
    //左右模糊
    QueryBuilders.queryStringQuery("fieldValue").field("fieldName");

    ② 常用的用于推荐相似内容的查询
    //如果不指定filedName,则默认全部,常用在相似内容的推荐上
    QueryBuilders.moreLikeThisQuery(new String[] {"fieldName"}).addLikeText("pipeidhua");
    ③ 前缀查询 如果字段没分词,就匹配整个字段前缀
    QueryBuilders.prefixQuery("fieldName","fieldValue");
    ④fuzzy query:分词模糊查询,通过增加fuzziness模糊属性来查询,如能够匹配hotelName为tel前或后加一个字母的文档,fuzziness 的含义是检索的
    term 前后增加或减少n个单词的匹配查询
    QueryBuilders.fuzzyQuery("hotelName", "tel").fuzziness(Fuzziness.ONE);
    ⑤ wildcard query:通配符查询,支持* 任意字符串;?任意一个字符
    //前面是fieldname,后面是带匹配字符的字符串
    QueryBuilders.wildcardQuery("fieldName","ctr*");
    QueryBuilders.wildcardQuery("fieldName","c?r?");

    • 1.3 范围查询

    ① 闭区间查询
    QueryBuilder queryBuilder0 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2");
    ② 开区间查询
    //默认是true,也就是包含
    QueryBuilder queryBuilder1 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2")
    .includeUpper(false).includeLower(false);
    ③ 大于
    QueryBuilder queryBuilder2 = QueryBuilders.rangeQuery("fieldName").gt("fieldValue");
    ④ 大于等于
    QueryBuilder queryBuilder3 = QueryBuilders.rangeQuery("fieldName").gte("fieldValue");
    ⑤ 小于
    QueryBuilder queryBuilder4 = QueryBuilders.rangeQuery("fieldName").lt("fieldValue");
    ⑥ 小于等于
    QueryBuilder queryBuilder5 = QueryBuilders.rangeQuery("fieldName").lte("fieldValue");

    • 1.4 组合查询/多条件查询/布尔查询

    QueryBuilders.boolQuery()
    QueryBuilders.boolQuery().must();//文档必须完全匹配条件,相当于and
    QueryBuilders.boolQuery().mustNot();//文档必须不匹配条件,相当于not
    QueryBuilders.boolQuery().should();//至少满足一个条件,这个文档就符合should,相当于or

    二、聚合查询
      ① 【概念】

    Elasticsearch有一个功能叫做 聚合(aggregations) ,它允许你在数据上生成复杂的分析统计。它很像SQL中的 GROUP BY 但是功能更强大。
    【注】 更好的理解概念,参考 https://blog.csdn.net/dm_vincent/article/details/42387161
    Buckets(桶):满足某个条件的文档集合。
    Metrics(指标):为某个桶中的文档计算得到的统计信息。
    就是这样!每个聚合只是简单地由一个或者多个桶,零个或者多个指标组合而成。
    通俗的讲可以粗略转换为SQL:select count(name) from table group by name
    以上的COUNT(name)就相当于一个指标。GROUP BY name 则相当于一个桶。
    桶和SQL中的组(Grouping)拥有相似的概念,而指标则与COUNT(),SUM(),MAX()等函数相似。
    1、桶(Buckets):一个桶就是满足特定条件的一个文档集合:
    一名员工要么属于男性桶,或者女性桶。
    城市Albany属于New York州这个桶。
    日期2014-10-28属于十月份这个桶。
    随着聚合被执行,每份文档中的值会被计算来决定它们是否匹配了桶的条件。如果匹配成功,那么该文档会被置入该桶中,同时聚合会继续执行。
    桶也能够嵌套在其它桶中,能让你完成层次或者条件划分这些需求。比如,Cincinnati可以被放置在Ohio州这个桶中,而整个Ohio州则能够被放置在美国这个桶中。
    ES中有很多类型的桶,让你可以将文档通过多种方式进行划分(按小时,按最流行的词条,按年龄区间,按地理位置,以及更多)。但是从根本上,它们都根据相同的原理运作:按照条件对文档进行划分。

    2、指标(Metrics):桶能够让我们对文档进行有意义的划分,但是最终我们还是需要对每个桶中的文档进行某种指标计算。分桶是达到最终目的的手段:提供了对文档进行划分的方法,从而让你能够计算需要的指标。多数指标仅仅是简单的数学运算(比如,min,mean,max以及sum),它们使用文档中的值进行计算。在实际应用中,指标能够让你计算例如平均薪资,最高出售价格,或者百分之95的查询延迟。

    3、聚合查询就是将两者结合起来,一个聚合就是一些桶和指标的组合。一个聚合可以只有一个桶,或者一个指标,或者每样一个。在桶中甚至可以有多个嵌套的桶。比如,我们可以将文档按照其所属国家进行分桶,然后对每个桶计算其平均薪资(一个指标)。因为桶是可以嵌套的,我们能够实现一个更加复杂的聚合操作:
    将文档按照国家进行分桶。(桶)
    然后将每个国家的桶再按照性别分桶。(桶)
    然后将每个性别的桶按照年龄区间进行分桶。(桶)
    最后,为每个年龄区间计算平均薪资。(指标)

      ② 聚合查询都是使用AggregationBuilders工具类创建,创建的聚合查询如下:

    (1)统计某个字段的数量
    ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");
    (2)去重统计某个字段的数量(有少量误差)
    CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");
    (3)聚合过滤
    FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));
    (4)按某个字段分组
    TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");
    (5)求和
    SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");
    (6)求平均
    AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");
    (7)求最大值
    MaxBuilder mb= AggregationBuilders.max("max_price").field("price");
    (8)求最小值
    MinBuilder min= AggregationBuilders.min("min_price").field("price");
    (9)按日期间隔分组
    DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");
    (10)获取聚合里面的结果
    TopHitsBuilder thb= AggregationBuilders.topHits("top_result");
    (11)嵌套的聚合
    NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");
    (12)反转嵌套
    AggregationBuilders.reverseNested("res_negsted").path("kps ");

    三、项目中的具体例子

    • 以下例子实现的功能是:使用es查询对数据进行分组然后求和统计,并且倒序排序

      ① 实体类:Project

      1 package com.adc.da.budget.entity;
      2 
      3 import com.adc.da.base.entity.BaseEntity;
      4 import com.adc.da.budget.annotation.MatchField;
      5 import com.adc.da.excel.annotation.Excel;
      6 import io.swagger.annotations.ApiModelProperty;
      7 import lombok.Getter;
      8 import lombok.Setter;
      9 import org.springframework.data.annotation.Id;
     10 import org.springframework.data.elasticsearch.annotations.Document;
     11 import org.springframework.data.elasticsearch.annotations.Field;
     12 import org.springframework.data.elasticsearch.annotations.FieldType;
     13 import org.springframework.format.annotation.DateTimeFormat;
     14 
     15 import java.text.Collator;
     16 import java.util.Date;
     17 import java.util.List;
     18 import java.util.Map;
     19 
     20 @Getter
     21 @Setter
     22 @Document(indexName = "financial_prd", type = "project")
     23 public class Project extends BaseEntity implements Comparable<Project> {
     24 
     25     public int compareTo(Project o) {
     26         return Collator.getInstance(java.util.Locale.CHINA).compare(this.getName(), o.getName());
     27     }
     28 
     29     //    matchField
     30 //     项目名称,业务方,人力投入,项目描述,所属业务,创建时间,项目负责人,人员
     31     //@Excel(name = "项目ID", orderNum = "1")
     32     @Id
     33     private String id;
     34 
     35     @Excel(name = "项目名称", orderNum = "1")
     36     @MatchField("项目名称")
     37     //@Field
     38     private String name;
     39 
     40     //项目负责人ID
     41     @MatchField(value = "项目负责人", checkId = true)
     42     private String projectLeaderId;
     43 
     44     @Excel(name = "项目负责人", orderNum = "2")
     45     @MatchField(value = "项目负责人")
     46     private String projectLeader;
     47 
     48     private String deptId;
     49 
     50     @Excel(name = "项目组成员", orderNum = "3")
     51     @MatchField("人员")
     52     private String projectMemberNames;
     53 
     54     private String[] memberNames;
     55 
     56     @MatchField(value = "人员", checkId = true)
     57     private String[] projectMemberIds;
     58 
     59     // @Excel(name = "所属业务ID", orderNum = "5")
     60     @MatchField("所属业务ID")
     61     private String budgetId;
     62 
     63     /**
     64      * 经营类 (OA 项目编号)
     65      */
     66     private String budgetDomainId;
     67 
     68     /**
     69      * 经营类 (OA 项目编号)
     70      */
     71     private String contractNoDomainId;
     72 
     73     @Excel(name = "所属业务", orderNum = "4")
     74     @MatchField("所属业务")
     75     private String budget;
     76 
     77     //@Excel(name = "业务类型ID", orderNum = "7")
     78     @MatchField("业务类型ID")
     79     private String businessId;
     80 
     81     @Excel(name = "业务类型", orderNum = "5")
     82     @MatchField("业务类型")
     83     private String business;
     84 
     85     @Excel(name = "业务方", orderNum = "6")
     86     @MatchField("业务方")
     87     private String projectOwner;
     88 
     89     @Excel(name = "创建时间", orderNum = "7", exportFormat = "yyyy-MM-dd HH:mm:ss", importFormat = "yyyy-MM-dd HH:mm:ss")
     90     @MatchField("创建时间")
     91     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     92     private Date startTime;
     93 
     94     @Excel(name = "项目开始时间", orderNum = "8", exportFormat = "yyyy-MM-dd HH:mm:ss", importFormat = "yyyy-MM-dd HH:mm:ss")
     95     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     96     private Date projectStartTime;
     97 
     98     @Excel(name = "项目完成状态", orderNum = "9")
     99     private String finishedStatus;
    100 
    101     @Excel(name = "人力投入(人/天)", orderNum = "10")
    102     @MatchField("人力投入")
    103     @Field(type = FieldType.Integer)
    104     private Integer personInput;
    105 
    106     //项目组成员  name 和 id
    107     private List<Map<String, String>> mapList;
    108 
    109     private List<Map<String,String>> userIdDeptNameMapList;
    110 
    111     private Date createTime;
    112 
    113     private Date modifyTime;
    114 
    115     //业务 n:1
    116     //质量考核 1:1
    117     private GualityInspection gualityInspection;
    118 
    119     //任务 1:n
    120     private List<Task> raskList;
    121 
    122     //收入费用 1:n
    123     private List<RevenueExpense> revenueExpenseList;
    124 
    125     // 支出费用 1:n
    126     private List<ExpensesIncurred> expensesIncurredList;
    127 
    128     @MatchField("项目描述")
    129     private String projectDescription;
    130 
    131     @Field(type = FieldType.String, analyzer = "not_analyzed")
    132     private String createUserId;
    133 
    134     private String createUserName;
    135 
    136     //删除标记
    137     private Boolean delFlag;
    138 
    139     @Field(type = FieldType.String, analyzer = "not_analyzed")
    140     private String pm;
    141 
    142     @Field(type = FieldType.String, analyzer = "not_analyzed")
    143     /*
    144      * 项目所属业务所在部门
    145      */
    146     private String projectTeam;
    147 
    148     @MatchField(value = "合同编号")
    149     private String contractNo;
    150 
    151     //业务创建人字段
    152     @Field(type = FieldType.String, analyzer = "not_analyzed")
    153     private String businessCreateUserId;
    154 
    155     //提供给前段判断是否能点状态按钮 0表示进行中
    156     private String btnFlag;
    157 
    158     private String approveUserId;
    159 
    160     @ApiModelProperty("是否具有修改权限")
    161     private Boolean manager;
    162 
    163     /**
    164      * 合同合计,转化为float型,存在失真,所以原始数据用 ,该字段仅应用于范围筛选
    165      *
    166      * @see #contractAmountStr
    167      */
    168     @Field(type = FieldType.Float)
    169     private float contractAmount;
    170 
    171     /**
    172      * 保证 表单中的数据精度
    173      * 项目搜索中 这个字段表示累计投入工时
    174      */
    175     private String contractAmountStr;
    176     /**
    177      * 开票金额
    178      */
    179     private List<ProjectContractInvoiceEO> projectContractInvoiceEOList;
    180 
    181     /**
    182      * 开始时间
    183      */
    184     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    185     private Date projectBeginTime;
    186 
    187     /**
    188      * 结束时间
    189      */
    190     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    191     private Date projectEndTime;
    192 
    193     /** 0. 经营类项目 , 1.日常类事务项目 , 2. 科研类项目*/
    194     private int projectType;
    195 
    196     private int projectYear;
    197 
    198     private int projectMonth;
    199 
    200     /**
    201      * 商务经理id
    202      *
    203      */
    204     private String businessManagerId;
    205 
    206     /**
    207      * 商务经理姓名
    208      *
    209      */
    210     private String businessManagerName;
    211 
    212 
    213 
    214     private String businessAdminId ;
    215 
    216     private String businessAdminName ;
    217 
    218     private String projectAdminId ;
    219 
    220     @Field(type = FieldType.String, analyzer = "not_analyzed")
    221     private String projectAdminName ;
    222 
    223     @Field(type = FieldType.String, analyzer = "not_analyzed")
    224     private String province;
    225 
    226     //乙方
    227     @Field(type = FieldType.String, analyzer = "not_analyzed")
    228     private String partyB;
    229 
    230 }

      ② 实现方法

      1 //根据业务方分组查询其合同金额并且倒序
      2 public List<Project> getProjectContractAmount(){
      3 
      4 /**本人觉得使用BoolQueryBuilder工具类也是可以的**/
      5 /* BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
      6 boolQueryBuilder.mustNot(QueryBuilders.termQuery(DEL_FLAG, true));
      7 
      8 boolQueryBuilder.must(QueryBuilders.termQuery("projectYear",projectYear));
      9 boolQueryBuilder.must(QueryBuilders.termQuery("projectType",0));
     10 
     11 TermsBuilder projectOwnerAgg = AggregationBuilders.terms("projectOwner").order(Terms.Order.aggregation("contractAmount", false));
     12 SumBuilder contractAmountAgg = AggregationBuilders.sum("contractAmount").field("contractAmount");
     13 
     14 TermsBuilder aa = projectOwnerAgg.subAggregation(contractAmountAgg);*/
     15 
     16 /**业务查询可以不用管Start**/
     17 String[] property = new String[1];
     18 property[0] = "0";
     19 List<BudgetEO> budgetEOList = budgetEODao.findAllBudgetNameNotLike("旧-%", property);
     20 //按中文首字母排序
     21 Collections.sort(budgetEOList);
     22 List<String> budgetIds = new ArrayList<>();
     23 for (BudgetEO budgetEO : budgetEOList) {
     24 budgetIds.add(budgetEO.getId());
     25 }
     26 
     27 Integer projectYear = Integer.parseInt(new SimpleDateFormat("yyyy").format(new Date()));
     28 /**业务查询可以不用管end**/
     29 
     30 //创建查询工具类,然后按照条件查询,可以分开也可以合在一起写
     31 QueryBuilder queryBuilder = QueryBuilders.boolQuery()
     32 .mustNot(QueryBuilders.termQuery(DEL_FLAG, true))
     33 .must(QueryBuilders.termQuery("projectYear",projectYear))
     34 .must(QueryBuilders.termQuery(PROJECT_TYPE,0))
     35 .must(QueryBuilders.termsQuery(BUDGET_ID, budgetIds));
     36 
     37 //构建查询
     38 NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(queryBuilder);
     39 
     40 /*
     41 //这里的作用是查询后显示指定的字段和不显示的字段设置
     42 
     43 String[] fileds = {"projectOwner","contractAmount"};
     44 nativeSearchQueryBuilder.withFields(fileds);
     45 String[] filterFileds = {"不需要显示的字段"}
     46 nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(fileds,filterFileds));
     47 
     48 */
     49 
     50 /**
     51 *创建聚合函数:本例子是以 projectOwner公司名称分组然后计算其contractAmount合同金额总和并且按照合同金额倒序排序
     52 * 相当于SQL语句 select projectOwner,sum(contractAmount) as contractAmount from peoject group by projectOwner order by contractAmount desc
     53 */
     54 TermsBuilder projectOwnerAgg =
     55 AggregationBuilders.terms("projectOwner").field("projectOwner").
     56 order(Terms.Order.aggregation("contractAmount",false));
     57 SumBuilder contractAmountAgg = AggregationBuilders.sum("contractAmount").field("contractAmount");
     58 nativeSearchQueryBuilder.addAggregation(projectOwnerAgg.subAggregation(contractAmountAgg));
     59 SearchQuery searchQuery = nativeSearchQueryBuilder.build();
     60 
     61 /***
     62 * 执行查询 参考网上有三种方式
     63 */
     64 /**
     65 * ① 通过reporitory执行查询,获得有Page包装了的结果集
     66 * //Page<Project> projects = projectRepository.search(searchQuery);
     67 */
     68 
     69 /**
     70 * ② 通过elasticSearch模板elasticsearchTemplate.queryForList方法查询
     71 * List<Project> projects = elasticsearchTemplate.queryForList(searchQuery, Project.class);
     72 */
     73 
     74 
     75 /**
     76 * ③ 通过elasticSearch模板elasticsearchTemplate.query()方法查询,获得聚合(常用)
     77 */
     78 Aggregations aggregations = elasticsearchTemplate.query(searchQuery, new ResultsExtractor<Aggregations>() {
     79 @Override
     80 public Aggregations extract(SearchResponse searchResponse) {
     81 return searchResponse.getAggregations();
     82 }
     83 });
     84 
     85 /**
     86 * 对查询结果处理
     87 */
     88 //转换成map集合
     89 Map<String, Aggregation> aggregationMap = aggregations.asMap();
     90 //获得对应的聚合函数的聚合子类,该聚合子类也是个map集合,里面的value就是桶Bucket,我们要获得Bucket
     91 StringTerms stringTerms = (StringTerms)aggregationMap.get("projectOwner");
     92 //获得所有的桶
     93 List<Terms.Bucket> buckets = stringTerms.getBuckets();
     94 
     95 /* 一 、使用Iterator遍历桶
     96 //将集合转换成迭代器遍历桶,当然如果你不删除buckets中的元素,直接foreach遍历就可以了
     97 Iterator<Terms.Bucket> iterator = buckets.iterator();
     98 List<String> projectOwnerList = new ArrayList<>();
     99 while (iterator.hasNext()){
    100 //bucket桶也是一个map对象,我们取它的key值就可以了
    101 String projectOwner = iterator.next().getKeyAsString();
    102 //根据projectOwner去结果中查询即可对应的文档,添加存储数据的集合
    103 projectOwnerList.add(projectOwner);
    104 }*/
    105 
    106 //② 使用 foreach遍历就可以了
    107 List<Project> projects = new ArrayList<>();
    108 for (Terms.Bucket bucket : buckets){
    109 Project project = new Project();
    110 String projectOwner = bucket.getKeyAsString();
    111 //得到所有子聚合
    112 Map<String, Aggregation> contractAmountSumbuilder = bucket.getAggregations().asMap();
    113 //sum值获取方法 如果是avg,那么这里就是Avg格式
    114 Sum contractAmounts = (Sum)contractAmountSumbuilder.get("contractAmount");
    115 double contractAmount = contractAmounts.getValue();
    116 
    117 project.setProjectOwner(projectOwner);
    118 project.setContractAmount((float) contractAmount);
    119 projects.add(project);
    120 
    121 }
    122 
    123 return projects;
    124 }


    四、参考博文地址

    https://www.cnblogs.com/xionggeclub/p/7975982.html

    https://blog.csdn.net/qq_40885085/article/details/105024625

    https://my.oschina.net/guozs/blog/716802

    https://blog.csdn.net/topdandan/article/details/81436141

    https://blog.csdn.net/justlpf/article/details/88105489

    https://blog.csdn.net/topdandan/article/details/81436141

  • 相关阅读:
    Table交替行变色 鼠标经过变色 单击变色
    编程专用字体(雅黑字体+Consolas)
    Enterprise Architect学习笔记-EA中关系
    通用分页存储过程
    解决vs2008无法切换设计视图
    盒子模式
    ASP.NET界面数据绑定大大杂烩
    Tyvj P1032 Begin2 Unit1 身份验证
    NOIP2010普及组T1
    TyvjBegin P1036 Begin2 Unit1 数独验证
  • 原文地址:https://www.cnblogs.com/zyh-2017/p/12781440.html
Copyright © 2011-2022 走看看