ElasticSearch的高级复杂查询:非聚合查询和聚合查询

Posted zyh-2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

 

以上是关于ElasticSearch的高级复杂查询:非聚合查询和聚合查询的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch全文检索技术 一篇文章即可从入门到精通(Elasticsearch安装,安装kibana,安装ik分词器,数据的增删改查,全文检索查询,聚合aggregations)(代码片

Elasticsearch增删改查

Elasticsearch7.8.0版本高级查询——桶聚合查询文档

Elasticsearch7.8.0版本入门——桶聚合查询文档(高级查询)

Elasticsearch7.8.0版本高级查询—— 聚合查询文档

MySQL表的高级增删改查