五 聚合
谈数据库呢,就一定要谈聚合啦。
聚合框架
用于对集合中的文档进行变换和组合。处理文档的东西叫做管道pipeline,管道由多个构件构成。构建包括了:filtering, projection, grouping, sorting, limiting, skipping。
假设有如此需求:
- 投影出所有作者
- 按照作者名称排序,统计每个作者名字出现次数
- 根据作者名字出现次数降序排列
- 返回结果限制为前五个
步骤为:
{'$project': {'author': 1}}
只投影出author字段。{'$group: {'_id': '$author', 'count': {'sum': 1}}'}
大家都知道group是干嘛用的了,就是把所有文档浓缩成一份的意思。合并文档的根据在这里被规定为author,并为每个文档新增字段count,内容是同作者名的文档的出现次数。{'$sort': {'count': -1}}
根据count进行降序排列。{'$limit': 5}
只要前五个文档。
要构建一个管道,需要把各种构件条件传入aggregate()
函数。
1 | db.test.aggregate({'$project': {'author': 1}}, {'$group: {'_id': '$author', 'count': {'$sum': 1}}, |
管道操作符
$match
相当于条件筛选,可以看作是find。
应尽可能地把match操作提前,减少后面的聚合工作量。
$project
投影。
默认的投影操作还会加入_id这个字段,如果不想要就把它设置为0。
1 | db.test.aggregate({'$project': {'age': 1, '_id': 0}}) |
除了将想要返回的字段设置成1,还可以用别的方式进行改名,比如:
1 | db.test.aggregate({'$project': {'userId': '$_id'}}) |
就是将_id映射为userId。
也可以映射数组中的第n个元素,只要{'$project': {'userId': '$fieldname.n'}}
。
有个问题,在映射为别的名字之后,就无法使用原来能使用的索引了,所以要用sort,就别乱映射成别的名字。
映射的基础功能是筛选一些自己要的字段,但是之外还能使用各种表达式来做更高阶的操作。
数学表达式
举例用法:
1 | db.test.aggregate( |
有加减乘除余5种算法:
- $add
- $subtract
- $multiply
- $divide
- $mod
日期表达式
我们当然知道数据库中有一种数据类型叫做date。运用日期表达式,你可以从某个日期中提取出年日月等你所需要的信息。
1 | db.test.aggregate( |
除了单纯的投影操作之外,还能结合算术表达式计算时间跨度。
1 | db.test.aggregate( |
字符表达式
- $substr 对象是数组,数组里是参数,第一个参数是字符串来源,第二个是开始index,第三个是结束index
- $concat
- $toLower
- $toUpper
1 | db.test.aggregate( |
逻辑表达式
- $cmp
- $strcasecmp
- $and
- $or
- $not
都没啥说的,简单易懂,不懂是傻Ⅹ。注意参数都是用数组形式包起来的就好。
- $cond[booleanExpr, trueExpr, falseExpr] 如果booleanExpr结果是真,就返回trueExpr,否则返回falseExpr
$group
分组。所谓分组,我尽量用人话描述一遍:
假设你要对属性x进行分组,在你的集合中x总共存在a、b、c三种情况。那么分组后返回给你的就是三个文档。文档的内容是什么呢?如果没有定义,那就是没有。但是如果比如定义了一个sum操作符,就可以累计所有同个x中的y字段的总和。
算术操作符
- $sum
- $avg
group操作的参数是一个dict,其中必定有一个字段叫做_id
,字段值就是你的分组依据,若是上文那种情况,那就是x。
1 | db.test.aggregate( |
极限操作符
在爆发的边缘试探。
- $max
- $min
- $first
- $last
假设求每个班里面分数最高的人。
1 | db.test.aggregate( |
如果上面的group已经根据score排过序了,那么用first或者last的性能会更高一些(这不是废话)。
$unwind
那么这里的最大的问题就是,这个英语是啥意思?!说到wind就想起风,风是它的名词意思,它的动词意思是缠绕,可以想象一下一股龙卷风在缠绕。unwind就是接触缠绕的意思,在MongoDB中可以把数组中的每一个值拆分成为单独的文档。
暂时想不出有什么卵用,反正记得有这么一个拆分数组为多个独立文档的功能就好了。
$sort
这都不用说啦。
$limit
见前文。
$skip
见前文。
管道使用优化
如果需要排序,那就尽量在第一阶段排序,利用好索引,索引只能在原有的集合中使用。如果单一的聚合操作内存占用超过20%,直接报错。
MapReduce
非常灵活的聚合工具,缺点是慢,不应该在项目运行过程中跑。
没啥了解的欲望,$skip: 1
。
聚合命令
count
1 | db.test.count() |
distinct
返回一个数组,内容是某个字段的所有不同结果(排除了相同的结果)。
假设集合名称test,要找出所有不同的age。
1 | db.runCommand({'distinct': 'test', 'key': 'age'}) |
group
不想看。