MongoTemplate 教程系列(三)

弱冠季首發表於2018-02-09

第三章: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

這部分結合需求來闡述:

  • 比如資料庫中儲存的資料如下:

MongoTemplate 教程系列(三)

  • 需求

    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中:

      MongoTemplate 教程系列(三)

      console中:

      MongoTemplate 教程系列(三)


    • 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中:

      MongoTemplate 教程系列(三)

      console中:

      MongoTemplate 教程系列(三)


    • 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中:

      MongoTemplate 教程系列(三)

      console中:

      MongoTemplate 教程系列(三)


    • 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中:

      MongoTemplate 教程系列(三)

      console中:

      MongoTemplate 教程系列(三)


    • 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中:

      MongoTemplate 教程系列(三)


官方文件傳送門

第一章:Document定義章

第二章:簡單的CRUD

相關文章