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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM