一、非聚合復雜查詢(這兒展示了非聚合復雜查詢的常用流程)
- 查詢條件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