MongoDB聚合(一) 聚合框架 mapreduce 命令:count,distinct,group 1. 聚合框架 使用聚合框架对集合中的文档进行变换和组合,可以用多个构件创建一个管道(pipeline),用于对一连串的文档进行处理。这些构件包括筛选(filtering),投射(projecting),分组(grouping),排序(sorting),限制(limiting),跳过(skipping)。 例如一个保存着动物类型的集合,希望找出最多的那种动物,假设每种动物被保存为一个mongodb文档,可以按照以下步骤创建管道。 1)将每个文档的动物名称映射出来。 2)安装名称排序,统计每个名称出现的次数。 3)将文档按照名称出现的次数降序排列。 4)将返回结果限制为前五个。 具体操作符: 1){"$porject", {"name" : 1}} 类似于查询阶段的字段选择器,指定"fieldname" : 1选定需要的字段,"fieldname" : 0排除不需要的字段,"_id"字段自动显示。结果保存在内存中,不会写入磁盘。 db.test_collection.aggregate({"$project" : {"name" : 1}}); => { "_id" : ObjectId("535a2d3c169097010b92fdf6"), "name" : "snake" } 2){"$group", {"_id" : "$name", "count" : {"$sum" : 1}}} 首先指定了分组的字段"name",该操作执行完后,每个name只对应一个结果,所有可以将name指定为唯一标识符"_id"。 第二个字段表明分组内的每个文档"count"字段加1。新加入的文档中不会有count字段。 db.test_collection.aggregate({"$project" : {"name" : 1}}, {"$group" : {"_id" : "$name", "count" : {"$sum" : 1}}}); => { "_id" : "bird", "count" : 8344 } { "_id" : "snake", "count" : 8443 } { "_id" : "cat", "count" : 8183 } { "_id" : "rabbit", "count" : 8206 } { "_id" : "tiger", "count" : 8329 } { "_id" : "cow", "count" : 8309 } { "_id" : "horse", "count" : 8379 } { "_id" : "dog", "count" : 8406 } { "_id" : "dragon", "count" : 8372 } { "_id" : "elephant", "count" : 8264 } { "_id" : "pig", "count" : 8403 } { "_id" : "lion", "count" : 8362 } 3){"$sort" : {"count" : -1}} 对结果集中的文档根据count字段做降序排列。 4){"$limit" : 5} 将返回结果限制为5个文档。 将上述结果综合起来: db.test_collection.aggregate( { "$project" : {"name" : 1}}, {"$group" : {"_id" : "$name", "count" : {"$sum" : 1}}}, {"$sort" : {"count" : -1}}, {"$limit" : 5} ); aggregate会返回一个文档数组,内容为出现次数最多的5个动物: { "_id" : "snake", "count" : 8443 } { "_id" : "dog", "count" : 8406 } { "_id" : "pig", "count" : 8403 } { "_id" : "horse", "count" : 8379 } { "_id" : "dragon", "count" : 8372 } 调试过程中。可以逐一对管道符进行排查。 聚合框架不能对集合进行写入操作,所有结果返回给客户端,聚合结果必须限制在16M以内。 2. 管道操作符 每个操作符都会接受一连串的文档,对这些文档进行类型转换,最后得到的文档作为结果传递给下一操作符。 不同的管道操作符可以将任意顺序组合在一起使用,而且可以被重复任意多次。 2.1 $match $match用于对文档集合进行筛选,之后得到的文档子集做聚合。 "$match"支持所有的常规查询操作符("$gt","$lt","$ne")等,不能使用地理空间操作符。 实际操作中尽量将"$match"放在管道的前面部分,一方面可以提快速将不需要的文档过滤掉,另外在映射和分组前筛选,查询可以使用索引。 2.2 $project 使用"$project"可以提取字段,可以重命名字段, db.foo.aggregate({"$project" : {"city" : 1, "_id" : 0}}) => { "city" : "NEW WORK" } 可以将投射过的字段重命名: db.foo.aggregate({"$project" : {"newcity" : "$city", "_id" : 0}}) => { "newcity" : "NEW WORK" } 使用"$fieldname"语法为了在聚合框架中引用fieldname字段,例如上面"$city"会被替换为"NEW WORK"。 对字段重命名后,Mongdb不会记录其记录字段的历史名称,所以应该在修改字段名称前使用索引。 2.2.1 管道表达式 可以使用表达式将多个字面量和变量组合为一个值。 可以使用组合或者任意深度的嵌套,创建复杂的表达式。 2.2.2 数学表达式 数学表示式用来操作数据运算。 db.foo.aggregate( {"$project" : {"total" : {"$add" : ["$age", "$year"]}, "_id" : 0 } } ) {"total" : 15} 可以将多个表达式组合为更为复杂的表达式: db.foo.aggregate( {"$project" : {"sub" : {"$subtract" : [{"$add" : ["$age", "$year"]}, 7]}, "_id" : 0 } } ) { "sub" : 8 } 操作符语法: 1)"$add" : [expr1, [, expr2, ..., exprN]] 将表达式相加 2)"$subtract" : [expr1, expr2] 表达式1减去表达式2 3)"$multiply" : [expr1, [, expr2, ..., exprN]] 将表达式相乘 4)"$divide" : [expr1, expr2] 表达式1除以表达式2得到商 5)"$mod" : [expr1, expr2] 表达式1除以表达式2得到余数 2.2.3 日期表达式 用于提取日期信息的表达式:"$year","$month","$week","$dayOfMonth","$dayOfweek","$hour","$minute","$second"。只能对日期类型的字段进行日期操作,不能对数值类型进行日期操作。 db.bar.insert({"name" : "pipi", "date" : new Date()}) db.bar.aggregate( {"$project" : {"birth-month" : {"$month" : "$date"}, "_id" : 0 } } ) { "birth-month" : 4 } 也可以使用字面量日期。 db.bar.aggregate( {"$project" : {"up-to-now" : {"$subtract" : [{"$minute" : new Date()}, {"$minute" : "$date"}]}, "_id" : 0 } } ) { "up-to-now" : 18 } 2.2.3 字符串表达式 操作符语法: 1)"$substr" : [expr, startOffset, numoReturn] 接受字符串,起始位置以后偏移N个字节,截取字符串。 2)"$concat" : [expr1[, expr2, ..., exprN]] 将给定的表达式连接在一起作为返回结果。 3)"$toLower" : expr 返回参数的小写形式 4)"$toUpper" : expr 返回参数的大写形式 例如: db.foo.insert({"firstname" : "caoqing", "lastname" : "lucifer"}) db.foo.aggregate( { "$project" : { "email" : { "$concat" : [ {"$substr" : ["$firstname", 0, 1]}, ".", "$lastname", "@gmail.com" ] }, "_id" : 0 } } ) { "email" : "[email protected]" } 2.2.3 逻辑表达式 操作符语法: 1)"$cmp" : [expr1, expr2] 比较两个参数,相等返回0,大于返回整数,小于返回负数。 2)"$strcasecmp" : [string1, string2] 比较字符串,区分大小写 3)"$eq"/"$ne"/"$gt"/"$gte"/"lt"/"lte" : [expr1, expr2] 比较字符串,返回结果(true or false) 4)"$and" : [expr1[, expr2, ..., exprN]] 所有值为true返回true,否则返回false。 5)"$or" : [expr1[, expr2, ..., exprN]] 任意表达式为true返回true,否则返回false 6)"$not" : expr 对表示式取反 还有两个控制语句。 "$crond" : [booleanExpr, trueExpr, falseExpr] 如果为true,返回trueExpr,否则,返回falseExpr。 "$ifFull" : [expr, replacementExpr] 如果expr为null,返回replacementExpr,否则返回expr。 算术操作符必须接受数值,日期操作符必须接受日期,字符串操作符必须接受字符串。 例如,根据学生出勤率(10%),平时作业(30%)和考试成绩(60%)得出最终成绩,如果是老师宠爱的学生,直接得100分: 插入数据: db.bar.insert( { "name" : "xiaobao", "teachersPet" : 1, "attendance" : 90, "quizz" : 80, "test" : 85 } ) db.bar.insert( { "name" : "caoqing", "teachersPet" : 0, "attendance" : 20, "quizz" : 50, "test" : 90 } ) db.bar.insert( { "name" : "pipi", "teachersPet" : 0, "attendance" : 100, "quizz" : 50, "test" : 10 } ) 聚合: db.bar.aggregate( { "$project" : { "grade" : { "$cond" : [ "$teachersPet", 100, { "$add" : [ {"$multiply" : [0.1, "$attendance"]}, {"$multiply" : [0.3, "$quizz"]}, {"$multiply" : [0.6, "$test"]}, ] } ] }, "_id" : 0 } } ) 返回结果: { "grade" : 100 } { "grade" : 71 } { "grade" : 31 }