第三章:Aggregate
在上一篇文章,講了spring-data-mongodb常用的增刪改查(CRUD)操作,但是平時我們除了這些簡單的操作外還需要進行一些複雜統計。本章就介紹mongodb 強大的聚合操作“Aggregate”。
一、Aggregate
MongoDB中聚合(aggregate)主要用於處理資料(諸如統計平均值,求和等),並返回計算後的資料結果。有點類似sql語句中的 count(*)。
mongodb語法
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
複製程式碼
mongoTemplate中定義的相關方法:
<O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType) <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, Class<O> outputType) <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, String inputCollectionName, Class<O> outputType) <T> ExecutableAggregationOperation.ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) <O> CloseableIterator<O> aggregateStream(Aggregation aggregation, Class<?> inputType, Class<O> outputType) <O> CloseableIterator<O> aggregateStream(Aggregation aggregation, String collectionName, Class<O> outputType) <O> CloseableIterator<O> aggregateStream(TypedAggregation<?> aggregation, Class<O> outputType) <O> CloseableIterator<O> aggregateStream(TypedAggregation<?> aggregation, String inputCollectionName, Class<O> outputType) 複製程式碼
二、Example
這部分結合需求來闡述:
- 比如資料庫中儲存的資料如下:
-
需求
Q1.統計各個年級人數
Q2.統計某個年級某一項測試在某個範圍的人數
Q3.統計某個年級某一項測試不在某個範圍的人數
Q4.統計各個測試專案得分的最大值,最小值,平均值
Q5.統計各個年級每一個專案得分的最大值,最小值,平均值
-
程式碼實現
-
Q1:"統計各個年級人數" 這個比較簡單,我們只需要按照年級分組然後進行sum就能得到結果
mongo:
db.pt.aggregate([ { "$group" : { "_id" : "$grade_name" , "總人數" : { "$sum" : 1}}}] ) 複製程式碼
java:
Aggregation aggregation1 = Aggregation.newAggregation(Aggregation.group("grade_name").count().as("總人數")); AggregationResults<BasicDBObject> outputTypeCount1 = mongoTemplate.aggregate(aggregation1, "pt", BasicDBObject.class); for (Iterator<BasicDBObject> iterator = outputTypeCount1.iterator(); iterator.hasNext(); ) { DBObject obj = iterator.next(); System.out.println(JSON.toJSONString(obj)); } 複製程式碼
結果:
mongo-vue中:
console中:
-
Q2:"統計某個年級某一項測試在某個範圍的人數" 這個也不難,只需要匹配 年級+測試專案+專案分數 between 分數1 and 分數2 然後根據年級分組統計
mongo:
db.pt.aggregate( [ { "$match" : { "grade_name" : "一年級"}} , { "$unwind" : "$items"} , { "$match" : { "items.item_name" : "BMI" , "items.score" : { "$gt" : 60 , "$lt" : 70}}} , { "$group" : { "_id" : "$grade_name" , "一年級BMI正常人數" : { "$sum" : 1}}}] ) 複製程式碼
java:
Aggregation aggregation4 = Aggregation.newAggregation( Aggregation.unwind("items"), Aggregation.match(Criteria.where("items.item_name").is("BMI").and("items.score").gt(60).lt(70)), Aggregation.group("grade_name").count().as("BMI正常人數")); AggregationResults<BasicDBObject> outputTypeCount4 = mongoTemplate.aggregate(aggregation4, "pt", BasicDBObject.class); for (Iterator<BasicDBObject> iterator = outputTypeCount4.iterator(); iterator.hasNext(); ) { DBObject obj = iterator.next(); System.out.println(JSON.toJSONString(obj)); } 複製程式碼
結果:
mongo-vue中:
console中:
-
Q3:"統計某個年級某一項測試不在某個範圍的人數" 這個和Q2的區別在於他是not between and,這我們需要使用or來出來,java中對應的是orOperator;
mongo:
db.pt.aggregate( [ { "$match" : { "grade_name" : "一年級"}} , { "$unwind" : "$items"} , { "$match" : { "items.item_name" : "BMI" , "$or" : [ { "items.score" : { "$lte" : 60}} , { "items.score" : { "$gte" : 70}}]}} , { "$group" : { "_id" : "$grade_name" , "BMI不正常人數" : { "$sum" : 1}}}] ) 複製程式碼
java:
Aggregation aggregation3 = Aggregation.newAggregation( Aggregation.match(Criteria.where("grade_name").is("一年級")), Aggregation.unwind("items"), Aggregation.match(Criteria.where("items.item_name").is("BMI").orOperator( Criteria.where("items.score").lte(60), Criteria.where("items.score").gte(70))), Aggregation.group("grade_name").count().as("BMI不正常人數")); AggregationResults<BasicDBObject> outputTypeCount3 = mongoTemplate.aggregate(aggregation3, "pt", BasicDBObject.class); for (Iterator<BasicDBObject> iterator = outputTypeCount3.iterator(); iterator.hasNext(); ) { DBObject obj = iterator.next(); System.out.println(JSON.toJSONString(obj)); } 複製程式碼
這裡有個問題需要注意:當定義多個Criteria (criteria1,criteria2,criteria3,criteria4)然後使用orOperator拼接的時候結果是不正確的,我實驗然後錯誤的寫法有兩種:
1. criteria1.orOperator(criteria2) //criteria1 criteria2 是並列條件滿足任一即可的【這個寫法是鐵定錯誤的!!!】 2. new Criteria().orOperator(criteria1,criteria2)//criteria1 criteria2 是並列條件滿足任一即可的【這個寫法百度說是正確寫法,不知道為什麼結果和上面的是一樣的】 複製程式碼
結果:
mongo-vue中:
console中:
-
Q4.“統計各個測試專案得分的最大值,最小值,平均值” 這個問題其實就是針對測試專案進行分組,然後使用 min max avg函式
mongo:
db.pt.aggregate( [ { "$match" : { "grade_name" : "一年級"}} , { "$unwind" : "$items"} , { "$group" : { "_id" : "$items.item_name" , "平均分" : { "$avg" : "$items.score"} , "最小值" : { "$min" : "$items.score"} , "最大值" : { "$max" : "$items.score"}}}] ) 複製程式碼
java:
Aggregation aggregation5 = Aggregation.newAggregation( Aggregation.match(Criteria.where("grade_name").is("一年級")), Aggregation.unwind("items"), Aggregation.group("$items.item_name").avg("$items.score").as("平均分").min("$items.score").as ("最小值").max("$items.score").as("最大值")); AggregationResults<BasicDBObject> outputTypeCount5 = mongoTemplate.aggregate(aggregation5, "pt", BasicDBObject.class); for (Iterator<BasicDBObject> iterator = outputTypeCount5.iterator(); iterator.hasNext(); ) { DBObject obj = iterator.next(); System.out.println(JSON.toJSONString(obj)); } 複製程式碼
使用原生語句的寫法:
//展開陣列 DBObject queryUnwind=new BasicDBObject("$unwind","$items"); //分組統計 DBObject groupObject=new BasicDBObject("_id",new BasicDBObject("item_name", "$items.item_name")); groupObject.put("min", new BasicDBObject("$min","$items.score")); groupObject.put("max", new BasicDBObject("$max","$items.score")); groupObject.put("avg", new BasicDBObject("$avg","$items.score")); DBObject queryGroup=new BasicDBObject("$group",groupObject); AggregationOutput output=mongoTemplate.getCollection("pt").aggregate(queryUnwind,queryGroup); for (Iterator<DBObject> iterator = output.results().iterator(); iterator.hasNext();) { DBObject obj =iterator.next(); System.out.println(obj.toString()); } 複製程式碼
結果:
mongo-vue中:
console中:
-
Q5 .“統計各個年級每一個專案得分的最大值,最小值,平均值” 這個問題需要我們按照年級和專案名分組 計算出每一個的最大值,最小值,平均值然後把每個年級的push到一起
這裡我直接使用類似原生語句的寫法,不在單獨寫出mongo語句怎麼寫。
java:
/* 建立 $unwind 操作, 用於切分陣列*/ DBObject unwind = new BasicDBObject("$unwind", "$items"); /* Group操作*/ DBObject groupFields = new BasicDBObject("_id",new BasicDBObject("grade_name", "$grade_name").append("item_name", "$items.item_name")); groupFields.put("min_score", new BasicDBObject("$min","$items.score")); groupFields.put("max_score", new BasicDBObject("$max","$items.score")); groupFields.put("avg_score", new BasicDBObject("$avg","$items.score")); DBObject group = new BasicDBObject("$group", groupFields); /* Reshape Group Result*/ DBObject projectFields = new BasicDBObject(); projectFields.put("grade_name", "$_id.grade_name"); DBObject subProjects=new BasicDBObject("item_name","$_id.item_name"); subProjects.put("min","$min_score"); subProjects.put("max","$max_score"); subProjects.put("avg","$avg_score"); projectFields.put("item_info",subProjects ); DBObject project = new BasicDBObject("$project", projectFields); /* 將結果push到一起*/ DBObject groupAgainFields = new BasicDBObject("_id", "$grade_name"); groupAgainFields.put("item_info", new BasicDBObject("$push", "$item_info")); DBObject reshapeGroup = new BasicDBObject("$group", groupAgainFields); /* 檢視Group結果 */ AggregationOutput output1 = mongoTemplate.getCollection("pt").aggregate(unwind, group, project, reshapeGroup); for (Iterator<DBObject> iterator = output1.results().iterator(); iterator.hasNext();) { DBObject obj =iterator.next(); System.out.println(obj.toString()); } 複製程式碼
結果: console中:
-