MongoDB高级查询[聚合Group]

接上一篇... 见: http://my.oschina.net/zhzhenqin/blog/97949

  • Group

为了方便我还是把我的表结构贴上来:

和数据库一样group常常用于统计。MongoDB的group还有很多限制,如:返回结果集不能超过16M, group操作不会处理超过10000个唯一键,好像还不能利用索引[不很确定]。

Group****大约需要一下几个参数。

  1. key:用来分组文档的字段。和keyf两者必须有一个
  2. keyf:可以接受一个javascript函数。用来动态的确定分组文档的字段。和key两者必须有一个
  3. initial:reduce中使用变量的初始化
  4. reduce:执行的reduce函数。函数需要返回值。
  5. cond:执行过滤的条件。
  6. finallize:在reduce执行完成,结果集返回之前对结果集最终执行的函数。可选的

下面我用Java对他们做一些测试。

我们以age年龄统计集合中存在的用户。Spring Schema和上次的一样。有了MongoTemplate对象我们可以做所有事的。以age统计用户测试代码如:

1@Test 2 public void testGroupBy() throws Exception { 3 String reduce = "function(doc, aggr){" + 4 " aggr.count += 1;" + 5 " }"; 6 Query query = Query.query(Criteria.where("age").exists(true)); 7 DBObject result = mongoTemplate.getCollection("person").group(new BasicDBObject("age", 1), 8 query.getQueryObject(), 9 new BasicDBObject("count", 0), 10 reduce); 11 12 Map map = result.toMap(); 13 System.out.println(map); 14 for (Map.Entry o : map.entrySet()) { 15 System.out.println(o.getKey() + " " + o.getValue()); 16 } 17 }

key为new BasicDBObject("age", 1)

cond为:Criteria.where("age").exists(true)。即用户中存在age字段的。

initial为:new BasicDBObject("count", 0),即初始化reduce中人的个数为count为0。假如我们想在查询的时候给每个年龄的人增加10个假用户。我们只需要传入BasicDBObject("count", 10).

reduce为:reduce的javascript函数

上面的执行输出如:

12 [age:23.0, count:1.0] 21 [age:25.0, count:1.0] 30 [age:24.0, count:1.0]

前面的是一个序号,是Mongo的java-driver加上去的。我们可以看到结果在后面。

不过你可能都觉得reduce这段代码用Java写的太繁琐了,要是和Python一样支持多行字符串多好啊。 我也烦。下面的例子我用Groovy写,不过我尽量写的贴近Java。

同样的reduce,用Groovy只需这样:

1def reduce = """ 2 function(doc, aggr){ 3 aggr.count += 1; 4 } 5 """;

用age统计用户这是基本的需求了。下面我来几个高级点的。

我的表结构中用户的朋友[myFriends]是一个数组类型的,mongo提供的查询中对数组查询时数组长度$size只能用来判断,却不能用来输出[至少我没找到]。那么我们用group操作来统计一下每个人有几个朋友。测试代码如:

1@Test 2 void testFriendGroupUserFriendCount() throws Exception { 3 def reduce = """ 4 function(doc, aggr){ 5 aggr.manId = doc.manId; 6 doc.myFriends.forEach(function(z){ 7 aggr.count += 1; 8 }) 9 } 10 """; 11 12 Query query = Query.query(Criteria.where("myFriends").exists(true)); 13 DBObject result = mongoTemplate.getCollection("person").group( 14 new BasicDBObject("manId", 1), 15 query.getQueryObject(), 16 new BasicDBObject("count", 0), 17 reduce); 18 19 Map map = result.toMap(); 20 for (Map.Entry o : map.entrySet()) { 21 System.out.println(o.getKey() + " ==> " + o.getValue()); 22 } 23 }

执行结果如:

12 ==> [manId:345678, count:2.0] 21 ==> [manId:234567, count:2.0] 30 ==> [manId:123456, count:4.0]

上面的reduce中遍历文档的数组用了forEach,我记得好像Javascript中不能这么做吧? 我对js不熟,希望牛人解答下。

上面的例子一直都没用到finallize,下面的测试我希望能用上。

我们统计每个人最喜欢水果是哪个?每个人都有n个自己的喜欢的水果,fruits.boost是每个水果的权重。那么我们找出最喜欢的那个?

测试代码:

1@Test 2 void testGroupByFruitFinallize() throws Exception { 3 def reduce = """ 4 function(doc, out) { 5 out.name = doc.manName; 6 for(i in doc.fruits) { 7 if(doc.fruits[i] in out.fruits) { 8 out.fruits[doc.fruits[i].fruitId]++; 9 } else { 10 out.fruits[doc.fruits[i].fruitId] = 1; 11 } 12 } 13 } 14 """; 15 16 def finallizer = """ 17 function(out) { 18 var mostPopular = 0; 19 for(i in out.fruits) { 20 if(out.fruits[i] > mostPopular) { 21 out.fruitId = i; 22 mostPopular = out.fruits[i]; 23 } 24 } 25 delete out.fruits; 26 return out; 27 } 28 """; 29 30 Query query = new BasicQuery("{}"); 31 long time = System.currentTimeMillis(); 32 DBObject result = mongoTemplate.getCollection("person").group(new BasicDBObject("fruits", true), 33 query.getQueryObject(), 34 new BasicDBObject("fruits", new BasicDBObject()), 35 reduce, 36 finallizer); 37 System.out.println("use time: " + (System.currentTimeMillis() - time)); 38 39 Map map = result.toMap(); 40 for (Map.Entry o : map.entrySet()) { 41 System.out.println(o.getKey() + " " + o.getValue()); 42 } 43 }

它的测试输出为:

12 [name:ZhenZi, fruitId:www] 21 [name:YangYan, fruitId:www] 30 [name:ZhenQin, fruitId:aaa]

OK,他完成了。

关于keyf我找了很多资料,目前还没发现怎么使用。希望有人能解答下。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )