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" : "c.lucifer@gmail.com" }
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 }
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" : "c.lucifer@gmail.com" }
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 }