ElasticSearch的高级复杂查询:非聚合查询和聚合查询
Posted zyh-2017
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ElasticSearch的高级复杂查询:非聚合查询和聚合查询相关的知识,希望对你有一定的参考价值。
一、非聚合复杂查询(这儿展示了非聚合复杂查询的常用流程)
- 查询条件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
以上是关于ElasticSearch的高级复杂查询:非聚合查询和聚合查询的主要内容,如果未能解决你的问题,请参考以下文章
Elasticsearch全文检索技术 一篇文章即可从入门到精通(Elasticsearch安装,安装kibana,安装ik分词器,数据的增删改查,全文检索查询,聚合aggregations)(代码片
Elasticsearch7.8.0版本高级查询——桶聚合查询文档
Elasticsearch7.8.0版本入门——桶聚合查询文档(高级查询)