mongoTemplate使用group函数进行分组和统计

1 简介

在工作的过程中,使用MongoDB存储业务数据,有时候想做的不仅仅是将数据从MongoDB中提取出来,可能需要对数据进行分析并加以利用。在党费收缴模块开发过程中,需要根据党支部名称进行分组,统计出党支部内人员的数目,每个党支部应缴党费和实缴党费的总和 等统计信息。
由于spring-data-mongodb-1.9.5.RELEASE版本太低,因此在调用mongoTemplate类型的aggregate聚合框架时,曝出如下问题

The 'cursor' option is required, except for aggregate with the explain argument

花费了较多的时间,最后选择不使用聚合框架aggregate,改用了如下函数实现了该功能

public DBObject group(DBObject key, DBObject cond, DBObject initial, String reduce) {
		return this.group(key, cond, initial, reduce, (String)null);
}

2 问题描述

2.1 数据库表

	// 21
{
    "_id": "5283e22c98dd4158a2bf378fb394ad7b",
    "_class": "DBObject",
    "name": "partydues",
    "cname": "党费收缴",
    "map": {
        "partyDues": 410,
        "assigneeXM": "霍建华",
        "year": "2019",
        "assigneeUserID": "8ef3f163242b4059a4187f3eaf07f7e1",
        "roleId": "009002",
        "dataType": "task",
        "assigneeUserName": "huojianhua",
        "pid": "3312512",
        "userId": "3e7b5e3d9b6f4c768524453115baca81",
        "partyDueShouldBe": 95.06,
        "cnName": "周杰伦",
        "month": "01",
        "createTime": "2018-11-27 16:28:41",
        "roleName": "党支部1",
        "taskId": "3312578",
        "username": "gaochao"
    }
}

// 22
{
    "_id": "9513e93b58534bbb8441cca2edabef25",
    "_class": "DBObject",
    "name": "partydues",
    "cname": "党费收缴",
    "map": {
        "partyDues": 711,
        "assigneeXM": "宋全恒",
        "year": "2019",
        "assigneeUserID": "cf8d1fd5c23f4778b7c0f537d6de2c41",
        "roleId": "009003",
        "dataType": "task",
        "assigneeUserName": "songquanheng",
        "pid": "3312512",
        "userId": "be2c971ee03b4a4ea2577f381329818f",
        "partyDueShouldBe": 95.06,
        "cnName": "沈万三",
        "month": "01",
        "createTime": "2018-11-27 16:29:30",
        "roleName": "党支部2",
        "taskId": "3312562",
        "username": "shenwanshan"
    }
}

// 23
{
    "_id": "470e1e258b0443d2aa4b1c6b3cce5561",
    "_class": "DBObject",
    "name": "partydues",
    "cname": "党费收缴",
    "map": {
        "partyDues": 100,
        "assigneeXM": "宋全恒",
        "year": "2019",
        "assigneeUserID": "cf8d1fd5c23f4778b7c0f537d6de2c41",
        "roleId": "009003",
        "dataType": "task",
        "assigneeUserName": "songquanheng",
        "pid": "3325001",
        "userId": "be2c971ee03b4a4ea2577f381329818f",
        "partyDueShouldBe": 95.06,
        "cnName": "周芷若",
        "month": "02",
        "createTime": "2018-11-27 17:09:00",
        "roleName": "党支部1",
        "taskId": "3325051",
        "username": "zhouzhiruo"
    }
}

2.2 shell命令

db.partyDues.aggregate([
	$match: {“map.month”: “11”, “map.year”: “2018”, “map.dataType”: “task”}, {
	“$group”: {
		_id: “$map.roleName”,
		“sumOfPartyDueShouldBe”: {“$sum”: “$map.partyDueShouldBe”},
		“sumOfPartyDues”: {“$sum”: “$map.partyDues”},
		“totalNumOfPerson”: {“$sum”: 1}}
}
])

2.3 Java源代码实现

相应的Java的源代码如下:

public JSONObject findPartyDuesByParty(String year, String month) {
        String reduce = "function(doc, pre) " +
                "{" +
                "pre.count+=1;" +
                "var partyDueShouldBe = doc.map.partyDueShouldBe;"+
                "partyDueShouldBe = typeof(partyDueShouldBe)=='string'?0:partyDueShouldBe;"+
                "pre.sumOfPartyDueShouldBe+=partyDueShouldBe;" +
                "pre.sumOfPartyDues += doc.map.partyDues;" +
                "}";
        Query query = new Query();
        query.addCriteria(new Criteria("map.year").is(year))
                .addCriteria(new Criteria("map.month").is(month))
                .addCriteria(new Criteria("map.dataType").is("task"));
        DBObject result = mongoTemplate.getCollection(PARTYDUES_TABLE).group(new BasicDBObject("map.roleName", 1).append("map.roleId", 0),
                query.getQueryObject(), new BasicDBObject("count", 0).append("sumOfPartyDueShouldBe", 0).append("sumOfPartyDues", 0),  reduce);
        if (null == result) {
            logger.info("findPartyDuesByParty cannot find partyDues Info using the reduce");
            return null;
        }
        JSONObject ret= JSONObject.fromObject(result.toMap());
        DecimalFormat df = new DecimalFormat("0.00");
        JSONObject res = new JSONObject();

        for (int i=0; i<ret.size(); i++) {
            JSONObject ele = ret.getJSONObject(String.valueOf(i));
            String key = ele.getString("map.roleName");
            double sumOfPartyDueShouldBe = ele.getDouble("sumOfPartyDueShouldBe");
            double sumOfPartyDues = ele.getDouble("sumOfPartyDues");
            sumOfPartyDueShouldBe = Double.parseDouble(df.format(sumOfPartyDueShouldBe));
            sumOfPartyDues = Double.parseDouble(df.format(sumOfPartyDues));

            //获取党费收缴负责人信息
            String roleId = ele.getString("map.roleId");

            Map<String, String> leaderMap = null;
            try {

                leaderMap = userService.getPartyLeaderInfoByRoleId(roleId);
            } catch (Exception e) {
                e.printStackTrace();

            }
            if (null == leaderMap) {
                logger.info("Program cannot find the leaderUserId using roleId: "+ roleId);
                return null;
            }
            String leaderXM = leaderMap.get("XM");
            String leaderPhone = leaderMap.get("PHONE");
            ele.put("sumOfPartyDueShouldBe", sumOfPartyDueShouldBe);
            ele.put("sumOfPartyDues", sumOfPartyDues);
            ele.put("year", year);
            ele.put("month", month);
            ele.put("partyLeaderName", leaderXM);
            ele.put("partyLeaderPhone", leaderPhone);
            res.put(key, ele);
        }
        return res;
    }

2.4 Group操作

作为使用Map-Reduce的替代方法进行数据聚合,程序员可以使用group操作,这个操作与SQL的group在查询格式上类似,因此在使用上更加方便。但使用group操作确实有一些限制,举例来说它不可使用在分享环境shared environment。并且以BSON格式返回完整的结果集,因此结果应该比较小,少于10000keys。

{ "_id" : ObjectId("4ec1d25d41421e2015da64f1"), "x" : 1 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f2"), "x" : 1 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f3"), "x" : 2 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f4"), "x" : 3 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f5"), "x" : 3 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f6"), "x" : 3 }

如果要以每行记录中的x进行分组,同时统计出每个x出现的次数。则对应的java代码如下:

GroupByResults<XObject> results = 
mongoTemplate.group("group_test_collection",                                                      GroupBy.key("x").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1 }"), XObject.class);

第一个参数为要进行聚合的集合名,第二个参数则是流式API,通过GroupBy类指定了group操作的属性。在例子中仅仅使用了initialDocument和reduceFuction方法。我们也可以指定一个key-function,以及一个finalizer作为流式 API一部分。如果你有多个keys要进行分组,则可以传递多个keys列表,以逗号分隔。
group操作的原生结果是一个JSON文档,看起来如下:

{
  "retval" : [ { "x" : 1.0 , "count" : 2.0} ,
               { "x" : 2.0 , "count" : 1.0} ,
               { "x" : 3.0 , "count" : 3.0} ] ,
  "count" : 6.0 ,
  "keys" : 3 ,
  "ok" : 1.0
}

在retval文档被映射到group方法中第三个参数,在这个情形下XObject类展示如下:

public class XObject {
private float x;
private float count;

public float getX() {
return x;
  }

public void setX(float x) {
this.x = x;
  }

public float getCount() {
return count;
  }

public void setCount(float count) {
this.count = count;
  }

@Override
public String toString() {
return “XObject [x=” + x + “ count = “ + count + “]”;
  }
}

也可以通过调用getRawResults在类GroupByResults类上获得原生结果成一个Document。

3 参考

https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.aggregation

4文档下载

https://download.csdn.net/download/lk142500/10815398

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值