很多时候,从数据库中查出来的数据不一定满足我们的需求,需要进行再次二次加工,比如求和,分组,排序,分页等。这里就由此展开,说一下MongoDB中如何处理聚合,常用的聚合框架,和标准sql做类比
1. 先提一下,何为聚合
-
概念和例子
其实单纯说概念,有点绕,但实际上已经用了很多次了,比如mysql中我们对结果集进行再次加工,举例来说select avg(salary) as total,employeeNo from employee group by employeeNo;
上边的sql中,对原始的字段做了group、avg 的二次加工处理,实际上这就是聚合的简单应用。
mongoDB,Nosql型数据库,也是引入了聚合(aggregate)的概念, 主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。 -
mongo中的聚合
mongodb,有一些简单的聚合命令(shell中使用),也有一些聚合的框架,引入管道处理聚合,也可以使用工具,比如MapReduce
2. 接下来,看一下,MongoDB的聚合框架
-
聚合框架用处
聚合框架是MongoDB设计出来服务于聚合的。在MongoDB中有一些构件,它们可以创建一个管道(pipeline),用来对文档进行变换组合。 -
什么是管道呢 :
在linux中,是首次接触管道的概念,用于将当前命令,当做下次命令的参数。不过,在MongoDB中 聚合管道 是处理文档,将文档处理完成之后,传给下一个管道继续处理,管道操作可以重复 -
常见的聚合操作,如下表
操作 | 含义 | mongodb语法例子 |
---|---|---|
projecting (投影) | 类比于标准sql中自定义查询字段,修改字段别名等。可以做到增加或删除域、重命名、创建计算结果和嵌套文档,修盖文档结构等 | {"$project":{“author”:1}} |
grouping(分组) | 右边的例子,相当于是sql :select by_user, sum(*) as num_tutorial from mycol group by by_user | {KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", num_tutorial : {$sum : 1}}} |
sorting(排序) | 如右边的例子,对结果集按照count进行排序 | {"$sort":{“count”:-1}} |
limiting(限制) | 限制MongoDB聚合管道返回的文档数 | {"$limit":5} |
skiping(跳过) | 在聚合管道中跳过指定数量的文档,并返回余下的文档 | { $skip : 5 } |
- projecting : 投影
“$project” 类似于sql中select的字段,可以自己制定返回的字段; 可以返回表达式; 可以重命名。所以这是非常强大的功能,比如下边几个例子
上边展示了,"$project" 如何选择返回的字段,因为它返回的是一个文档列表,它可以制定那些字段需要,哪些不想要,因此,我把它理解为可以修改文档结构//查询需要的字段 db.peo.aggregate({"$project":{"author":1}}) //返回结果 : objectid 省略了 { "result" : [ { "_id" : objectId(), "author" :'lisi' }, { "_id" : objectId(), "author" : 'zhangsan' } ], "ok" : 1 } // _id字段是默认返回的,如果不想要,可以用如下的语句 db.peo.aggregate({"$project":{"author":1,"_id":0}})
实际上,它的操作性很高,比如可以重命名字段,例如
需要注意一点,在以前的字段上建的索引,重命名后是无法使用的。很奇怪,命名还是一个字段。//重命名字段 : 下边把author字段重命名为authorMobile db.peo.aggregate({"$project":{"authorMobile":"$author"}})
除了重命名、增删字段外,“$project”还可以返回表达式,如下
实际上,字段加减乘除都可以,此外也有一些时间函数、字符串函数,如下表db.peo.aggregate({"$project":{"totalPay":{"$add":["$salary":"$bonus"]}}}) //上边就是计算雇的员工实际开销 : 薪水+奖金 ,新建一个字段totalPay
语法 | 含义 |
---|---|
{“$add”:[expr1,expr2…]} | 字段相加 |
{“$subtract”:[expr1,expr2]} | expr1-expr2 : 减法 |
{“$multiply”:[expr1,expr2…]} | 字段相乘 |
{“$divide”:[expr1,expr2]} | expr1/expr2 : 除法 |
{“$mod”:[expr1,expr2]} | expr1%expr2 : 取余 |
{“$year”:"$colName"} | 仅可以对日期字段做日期函数操作 |
{“$substr”:[expr1,startoffset,numToReturn]} | 截取字符串,从startoffset开始,截取numToReturn位 |
{“$concat”:[expr1,expr2…exprN]} | 多个字符串拼接 |
{“$toLower”:expr} | 把字符串值变小写,大写(toUpper)用法同理 |
{“$con”:[booleanExpr,trueExpr,falseExpr]} | 条件判断,类似case when ,如果booleanExpr为真,返回trueExpr,否则返回falseExpr |
{“$ifNull”:[expr,replaceExpr]} | 条件判断,如果expr为空,就返回replaceExpr |
值得注意一点,这些条件都可以自由组合,以便于返回满足实际需要的字段
- group :分组
效果类似于标准sql中的group, 可以利用选定的字段,完成聚合操作,如下
一般情况下,分组都是结合分组函数使用,才有实际意义,可以结合如下操作{"$group":{"_id":"$day"}} //把day选做分组字段
其他类似的函数 : “$max”,"$avg","$min","$last"等,原理类似,就不多说了//计算每个国家的总收入 {"$group":{"_id":"$country","totalRevenue":{"$sum":"$revenue"}}} //按照国家分组,相同国家的revenue进行求和,相当于下边的sql select sum(revenue) as totalRevenue form world group by country
- 其他的一些构件
基本上类似标准sql, 语法也很简单,比如{"$sort":{"name":1}} //按照name字段升序排列,降序是-1 {"$limit":5} //限制返回的文档数为5 {"$skip":6}//跳过前六条document
- mongodb 调用这些管道操作
上边整理了,mongodb常用的聚合管道,在实际调用的时候,只需要把这些管道操作传到aggregate函数即可,如下
mongoDB要求,使用管道之前,(比如 “$project”) 尽量先完成数据过滤db.article.aggregate( { $project : { _id : 0 , title : 1 , author : 1 }});