管道操作符

每个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传给下一个操作符。

$match

对文档进行筛选,之后在筛选得到的文档子集上做聚合。$match 可以使用所有常规操作符($gt、$lt、$in等)。不能在 $match 中使用地理空间操作符。在实际使用时将 $match 放在管道的前面位置。这样做有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果在投射和分组前执行 $match ,查询可以使用索引。

$project

从子文档中提取字段,可以重命名字段,可以在这些字段上进行其他操作。

通过投射进行重命名

  1. > db.users.aggregate({"$project":{"userId":"$_id","_id":0})
  2. {
  3. "result":[
  4. {"userId":ObjectId("50e4b32427b160e099ddbee7")},
  5. {"userId":ObjectId("50e4b32427b160e099ddbee8")}
  6. ],
  7. "OK":1
  8. }

对字段进行重命名时,MongoDB 不会记录字段的历史名称,尽量在修改字段名称之前使用索引。

管道表达式

可以使用表达式将多个字面量和变量在一个值中使用。

数学表达式

算数表达式用来操作数值。下面表达式会将 salary 和 bonus 字段的值相加。

  1. > 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]

用第一个表达式除以第二个表达式的余数作为结果

日期表达式

只能对日期类型的字段做日期操作,不能对数值类型字段做日期操作。

每种日期类型的操作都是类似的:接收一个日期表达式,返回一个数值。

返回每个雇员入职的月份

  1. > db.employess.aggregate({
  2. {
  3. "$project":{
  4. "hiredIn":{"$month":"$hireDate"}
  5. }
  6. }
  7. })

字符串表达式

有一些基本操作符可以使用,它们的签名如下

  • $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

每个国家的总收入

  1. > db.sales.aggregate({
  2. {
  3. "$group":{
  4. "_id":"$country",
  5. "totalRevenue" : {"$sum":"$revenue"}
  6. }
  7. }
  8. })
  • $avg :value

返回每个分组的平均值

  1. > db.sales.aggregate({
  2. {
  3. "$group":{
  4. "_id":"$country",
  5. "totalRevenue" : {"$avg":"$revenue"},
  6. "numSales" : {"$sum":1}
  7. }
  8. }
  9. })

极限操作符

  • $max :expr

返回分组内的最大值。

  • $min:expr

返回分组内的最小值。

  • $first :expr

返回分组的第一个值,忽略后面所有的值。只有排序之后,明确知道数据顺序时这个操作才有意义。

  • $last :expr

$first 相反,返回分组后的最后一个值。

数据操作符

进行数组操作

  • $addToSet:expr

如果当前数组不包含 expr,那就将它添加到数组中。在返回结果集中,每个元素最多出现一次,而且元素的顺序是不确定的。

  • $apush:expr

不管 expr 是什么值,都将它添加到数组中。返回包含所有值的数组。

分组行为

大部分操作符的工作方式是流式的,只要有新文档插入,就可以对新文档进行处理,但是 $group 必须等到收到所有的文档后,才能对文档进行分组,才能将各个分组发送给管道中的下一个文档。

$unwind

将数值中的吗,每个值拆分成单独的文档。

  1. > db.blog.aggregate({"$unwind" : "$comments"})
  2. {
  3. "results":
  4. {
  5. "results":[
  6. {
  7. "_id":ObjectId("......"),
  8. "author":"K",
  9. "post":"Hello,world!",
  10. "comments":{
  11. "author":"mark",
  12. "date":ISODate("2013-01-10T17:52:04.148Z"),
  13. "text":"Nice post"
  14. }
  15. },
  16. {
  17. "_id":ObjectId("......"),
  18. "author":"K",
  19. "post":"Hello,world!",
  20. "comments":{
  21. "author":"mark",
  22. "date":ISODate("2013-01-10T17:52:04.148Z"),
  23. "text":"Nice post"
  24. }
  25. }
  26. ],
  27. "ok":1
  28. }
  29. }

$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权威指南