管道操作符
每个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传给下一个操作符。
$match
对文档进行筛选,之后在筛选得到的文档子集上做聚合。$match
可以使用所有常规操作符($gt、$lt、$in等)。不能在 $match
中使用地理空间操作符。在实际使用时将 $match
放在管道的前面位置。这样做有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果在投射和分组前执行 $match
,查询可以使用索引。
$project
从子文档中提取字段,可以重命名字段,可以在这些字段上进行其他操作。
通过投射进行重命名
> db.users.aggregate({"$project":{"userId":"$_id","_id":0})
{
"result":[
{"userId":ObjectId("50e4b32427b160e099ddbee7")},
{"userId":ObjectId("50e4b32427b160e099ddbee8")}
],
"OK":1
}
对字段进行重命名时,MongoDB 不会记录字段的历史名称,尽量在修改字段名称之前使用索引。
管道表达式
可以使用表达式将多个字面量和变量在一个值中使用。
数学表达式
算数表达式用来操作数值。下面表达式会将 salary 和 bonus 字段的值相加。
> db.employees.aggregate({"$project":{"totalPay":{"$add":["$salary","$bonus"]}})
操作符语法
- $add :[expr1,[expr2, … , exprN]]
表达式相加
- $subtract :[expr1,expr2]
用第一个表达式减去第二个表达式作为结果
- $multiply :[expr1,expr2, … , exprN]
表达式相乘
- $divide :[expr1,expr2]
用第一个表达式除以第二个表达式的商作为结果
- $mod :[expr1,expr2]
用第一个表达式除以第二个表达式的余数作为结果
日期表达式
只能对日期类型的字段做日期操作,不能对数值类型字段做日期操作。
每种日期类型的操作都是类似的:接收一个日期表达式,返回一个数值。
返回每个雇员入职的月份
> db.employess.aggregate({
{
"$project":{
"hiredIn":{"$month":"$hireDate"}
}
}
})
字符串表达式
有一些基本操作符可以使用,它们的签名如下
- $substr :[expr, startOffset, numToReturn]
expr 必须是字符串,这个操作会截取这个字符串的子串,从第 startOffset 字节开始到 numToReturn 结束。
- $concat :[expr1,expr2, … , exprN]
将给定的表达式(或者字符串)连接在一起作为返回结果。
- $toLower :expr
参数 expr 必须是个字符串值,这个操作返回 expr 的小写形式。
- $toUpper :expr
参数 expr 必须是个字符串值,这个操作返回 expr 的大写形式。
逻辑表达式
比较表达式
- $cmp :[expr1, expr2]
比较 expr1 和 expr2 。如果 expr1 等于 expr2,返回 0;如果 expr1 < expr2, 返回一个负数;如果 expr1 > expr2, 返回一个正数;
- $strcasecmp :[string1, string2]
比较 string1 和 string2, 区分大小写。只对罗马字符组成的字符串有戏。
- $eq/$ne/$gt/$gte/$lt/$lte :[expr1, expr2]
对 expr1 和 expr2 执行对应的比较操作,返回比较的结果(true 或者 false)。
布尔表达式
- $and :[expr1, expr2, … , exprN]
如果表达式的值都是 true ,那就返回 true ,否则返回 false。
- $or :[expr1, expr2, … , exprN]
只要有任意表达式的值为 true,就返回 true,否则就返回 false。
- $not :expr
对 expr 取反。
控制语句
- $cond :[booleanExpr. trueExpr, falseExpr]
如果 booleanExpr 的值是 true,那就返回 trueExpr,否则返回 falseExpr。
- $ifNull :[expr, replacementExpr]
如果 expr 是 null,返回 replacementExpr,否则返回 expr。
$group
将文档根据特定字段的不同值进行分组。
分组操作符
对每个分组进行计算,得到相应的结果。
算数操作符
对数组类型的值进行计算。
- $sum :value
每个国家的总收入
> db.sales.aggregate({
{
"$group":{
"_id":"$country",
"totalRevenue" : {"$sum":"$revenue"}
}
}
})
- $avg :value
返回每个分组的平均值
> db.sales.aggregate({
{
"$group":{
"_id":"$country",
"totalRevenue" : {"$avg":"$revenue"},
"numSales" : {"$sum":1}
}
}
})
极限操作符
- $max :expr
返回分组内的最大值。
- $min:expr
返回分组内的最小值。
- $first :expr
返回分组的第一个值,忽略后面所有的值。只有排序之后,明确知道数据顺序时这个操作才有意义。
- $last :expr
与 $first
相反,返回分组后的最后一个值。
数据操作符
进行数组操作
- $addToSet:expr
如果当前数组不包含 expr,那就将它添加到数组中。在返回结果集中,每个元素最多出现一次,而且元素的顺序是不确定的。
- $apush:expr
不管 expr 是什么值,都将它添加到数组中。返回包含所有值的数组。
分组行为
大部分操作符的工作方式是流式的,只要有新文档插入,就可以对新文档进行处理,但是 $group
必须等到收到所有的文档后,才能对文档进行分组,才能将各个分组发送给管道中的下一个文档。
$unwind
将数值中的吗,每个值拆分成单独的文档。
> db.blog.aggregate({"$unwind" : "$comments"})
{
"results":
{
"results":[
{
"_id":ObjectId("......"),
"author":"K",
"post":"Hello,world!",
"comments":{
"author":"mark",
"date":ISODate("2013-01-10T17:52:04.148Z"),
"text":"Nice post"
}
},
{
"_id":ObjectId("......"),
"author":"K",
"post":"Hello,world!",
"comments":{
"author":"mark",
"date":ISODate("2013-01-10T17:52:04.148Z"),
"text":"Nice post"
}
}
],
"ok":1
}
}
$sort
如果对大量文档进行排序请放在管道的第一阶段进行排序,这时的排序操作可以使用索引,否则会非常慢。
> db.employees.aggregate({
"$project" : {
"compensation":{
"$add",["$salary","$bonus"]
},
"name":1
},
{
"$sort":{
"compensation":-1,
"name":1
}
}
})
$limt
接受一个数字 n,返回结果集中的前 n 个文档。
$skip
接受一个数字 n,丢弃结果集中的前 n 个文档,将剩余文档作为结果返回。
MapReduce
一种聚合工具,强大、灵活,但是非常慢,不应该使用在实时的数据分析中。
找出集合中有所有的键
> map = function() {
for(var key in this){
emit(key,{count:1});
};
};
> reduce = function() {
total = 0;
for(var v in emits){
total += emits[i].count;
};
return {"count" : total}
};
> db.runCommand({"mapreduce":"foo","map":map,"reduce":reduce})
{
"result":"tmp.mr.mapreduce_126787811_1",
"timeMillis":12,
"counts":{
"input":6,
"emit":14,
"output":5,
}
}
MapReduce 返回的文档包含很多与操作有关的源信息
- “result”:”tmp.mr.mapreduce_126787811_1”
存放 MapReduce 结果的集合名。这是个临时集合,在 MapReduce 的连接关闭后它就被自动删除了。
- “timeMillis”:12
操作花费的时间,单位是毫秒。
- “counts”:{ … }
这个内嵌文档主要用于调试
- “input”:6
发送到 map 函数的文档个数
- “emit”:14
在 map 中 emit 被调用的次数。
- “output”:5
结果集合中的文档数量。
MapReduce 的可选键
- “finalize”:function
将 reduce 的结果发送给这个键,是整个过程的最后一步。
- “keeptemp”:boolean
如果为 true ,关闭连接时会将临时结果集合保存下来,否则不保存。
- “out”:string
输出集合的名称。系统自动设置 keeptemp 为 true 。
- “query”:document
发送 map 前,先用指定条件过滤文档。
- “sort”:document
发送 map 前先给文档排序。
- “limit”:integer
发送 map 函数的文档数量的上线。
- “scope”:document
在 JavaScript 代码中使用的变量。
- “verbose”:boolean
是否记录详细的服务器日志。
保存数据集合
默认情况下,MongoDB 执行 MapReduce 创建一个临时集合,如果想保存它,设 keeptemp 为 true 。
out 选项可以给这个集合重命名,这时会自动设置 keeptemp 为 true 。
聚合命令
cout
用于返回集合的文档数量
> db.foo.count()
0
distinct
用来找出给定键的所有不同值。
> db.runCommand({"distinct":"people","key":"age"})
{"values":[20,35,60],"ok":1}
group
> db.runCommand({"group":{
"ns":"stocks",
"key":"day",
"initial":{"time":0},
"$reduce":function(doc,prev) {
if(doc.time>prev.time){
prev.price = doc.price;
prev.time = doc.time;
}
},
}})
- “ns”:”stocks”
指定要进行分组的集合。
- “key”:”day”
指定文档分组依据的键。
- “initial”:{“time”:0}
每一个组 reduce 函数调用中的初始化 time 值,会作为初始化文档传递给后续过程。
每一组的成员都使用这个累加器,所以它的任何变化都可以保留下来。
- “$reduce”:function(doc,prev) { … }
这个函数会在集合的每个文档上执行。传递两个参数,当前文档和累加文档。
参考
[1] MongoDB权威指南