Spring Data MongoDB 使用group和unwind實現對陣列資料進行簡單統計

肖老闆發表於2017-08-23

Spring Data MongoDB 使用group和unwind實現對陣列資料進行簡單統計

先簡單交代一下需求:新高考政策下,高中學生可以組合選擇自己喜歡的三門副科,選科結束後教務需要看到這次選科的單科統計資訊。資料庫中儲存的資料結構如下:

{
    "_id" : ObjectId("599bc8be5ae9fb99b2ca2499"),
    "_class" : "com.xxx.db.model.SelectCourseRecord",
    "selectCourse" : "599bc8be5ae9fb99b2ca2498",
    "student" : "5993b03f8a4f1a5ba4e05d08",
    "courses" : [ 
        "地理", 
        "物理", 
        "政治"
    ],
    "removed" : false,
    "status" : "success",
    "createdTime" : NumberLong(1503381694777)
},
{
    "_id" : ObjectId("599bc8be5ae9fb99b2ca249a"),
    "_class" : "com.xxx.db.model.SelectCourseRecord",
    "selectCourse" : "599bc8be5ae9fb99b2ca2498",
    "student" : "599a8faf8a4f1a26dcfef389",
    "courses" : [ 
        "化學", 
        "物理", 
        "政治"
    ],
    "removed" : false,
    "status" : "success",
    "createdTime" : NumberLong(1503381694795)
}

也就是我們要得到選了物理、地理、化學和政治的各有多少人,如果科目不是放在陣列裡面的,那很簡單,直接用group就可以得到結果了。如果直接對coursesgroup,你可能會得到一個莫名其妙的結果!針對這種情況,MongoDB為我們準備了unwind方法。unwind方法會將陣列解開,比如上面的第一個資料,如果對其unwind的話,可以理解為我們會得到下面這樣的結果:

{
    "_id" : ObjectId("599bc8be5ae9fb99b2ca2499"),
    "_class" : "com.xxx.db.model.SelectCourseRecord",
    "selectCourse" : "599bc8be5ae9fb99b2ca2498",
    "student" : "5993b03f8a4f1a5ba4e05d08",
    "courses" : "地理",
    "removed" : false,
    "status" : "success",
    "createdTime" : NumberLong(1503381694777)
},
{
    "_id" : ObjectId("599bc8be5ae9fb99b2ca2499"),
    "_class" : "com.xxx.db.model.SelectCourseRecord",
    "selectCourse" : "599bc8be5ae9fb99b2ca2498",
    "student" : "5993b03f8a4f1a5ba4e05d08",
    "courses" : "物理",
    "removed" : false,
    "status" : "success",
    "createdTime" : NumberLong(1503381694777)
},
{
    "_id" : ObjectId("599bc8be5ae9fb99b2ca2499"),
    "_class" : "com.xxx.db.model.SelectCourseRecord",
    "selectCourse" : "599bc8be5ae9fb99b2ca2498",
    "student" : "5993b03f8a4f1a5ba4e05d08",
    "courses" : "政治",
    "removed" : false,
    "status" : "success",
    "createdTime" : NumberLong(1503381694777)
}

注意看,除了courses這一項不一樣,其他的值都是一樣的!那麼此時我們再來對courses進行group的話就可以了。完整的Java程式碼如下:

@Service
public class DefaultSelectCourseRecordService extends AbstractService implements ISelectCourseRecordService {
    @Override
    public Map<String, Long> countSubjectSelected(String selectCourseId) {
        Aggregation agg = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("selectCourse").is(selectCourseId)),
                Aggregation.project("courses"),
                Aggregation.unwind("courses"),
                Aggregation.group("courses").count().as("count"),
                Aggregation.project("count").and("courses").previousOperation(),
                Aggregation.sort(Sort.Direction.DESC, "count")
                );
        AggregationResults<SubjectCount> groupResults = mongoTemplate.aggregate(agg, SelectCourseRecord.class, SubjectCount.class);
        List<SubjectCount> subjectCounts = groupResults.getMappedResults();

        Map<String, Long> subjectCountMap = subjectCounts.stream().collect(Collectors.toMap(t -> t.getCourses(), t -> t.getCount()));

        return subjectCountMap;
    }

    private class SubjectCount {

        private String courses;

        private long count;

        public String getCourses() {
            return courses;
        }

        @SuppressWarnings("unused")
        public void setCourses(String courses) {
            this.courses = courses;
        }

        public long getCount() {
            return count;
        }

        @SuppressWarnings("unused")
        public void setCount(long count) {
            this.count = count;
        }
    }
}

聚合操作返回結果是SubjectCountList,用了Java8 Stream轉為了方便一點的Map,這裡的返回結果格式如下:

{
    "政治": 10,
    "物理": 10,
    "生物": 9,
    "歷史": 9,
    "化學": 10,
    "地理": 9
}

參考文件: https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

相關文章