【MongoDB权威指南】五

五 聚合

谈数据库呢,就一定要谈聚合啦。

聚合框架

用于对集合中的文档进行变换和组合。处理文档的东西叫做管道pipeline,管道由多个构件构成。构建包括了:filtering, projection, grouping, sorting, limiting, skipping。

假设有如此需求:

  1. 投影出所有作者
  2. 按照作者名称排序,统计每个作者名字出现次数
  3. 根据作者名字出现次数降序排列
  4. 返回结果限制为前五个

步骤为:

  1. {'$project': {'author': 1}}
    只投影出author字段。
  2. {'$group: {'_id': '$author', 'count': {'sum': 1}}'}
    大家都知道group是干嘛用的了,就是把所有文档浓缩成一份的意思。合并文档的根据在这里被规定为author,并为每个文档新增字段count,内容是同作者名的文档的出现次数。
  3. {'$sort': {'count': -1}}
    根据count进行降序排列。
  4. {'$limit': 5}
    只要前五个文档。

要构建一个管道,需要把各种构件条件传入aggregate()函数。

1
2
db.test.aggregate({'$project': {'author': 1}}, {'$group: {'_id': '$author', 'count': {'$sum': 1}},
{'$sort': {'count': -1}}, {'$limit': 5})

管道操作符

$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
2
3
4
5
6
7
8
9
db.test.aggregate(
{
'project': {
'totalPay': {
'$add' ['$salary', '$bonus']
}
}
}
)

有加减乘除余5种算法:

  • $add
  • $subtract
  • $multiply
  • $divide
  • $mod

日期表达式

我们当然知道数据库中有一种数据类型叫做date。运用日期表达式,你可以从某个日期中提取出年日月等你所需要的信息。

1
2
3
4
5
6
7
db.test.aggregate(
{
'$project': {
'theMonth': {'$month': '$theDate'}
}
}
)

除了单纯的投影操作之外,还能结合算术表达式计算时间跨度。

1
2
3
4
5
6
7
8
9
10
11
12
db.test.aggregate(
{
'$project': {
'duration': {
'substract': [
{'$year': new Date()},
{'$year': '$theDate'}
]
}
}
}
)

字符表达式

  • $substr 对象是数组,数组里是参数,第一个参数是字符串来源,第二个是开始index,第三个是结束index
  • $concat
  • $toLower
  • $toUpper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.test.aggregate(
{
'$project': {
'email': {
'$concat': [
{'$substr': ['$firstName', 0, 1]},
'.',
'$lastName',
'@example.com'
]
}
}
}
)

逻辑表达式

  • $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
2
3
4
5
6
7
8
db.test.aggregate(
{
'$group': {
'_id': '$x',
'count': {'$sum': '$y'}, // 所有y字段的总和
}
}
)

极限操作符

在爆发的边缘试探。

  • $max
  • $min
  • $first
  • $last

假设求每个班里面分数最高的人。

1
2
3
4
5
6
7
8
9
10
db.test.aggregate(
{
'$group': {
'_id': '$classNo',
'bestStudent': {
'$max': '$score'
}
}
}
)

如果上面的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

不想看。

Buy Me A Coffee / 捐一杯咖啡的钱
分享这篇文章~
0%
//