您的位置:首页 > 数据库 > Mongodb

MongoDB 聚合

2015-08-24 10:50 609 查看

聚合框架

    先看一个示例:有一个保存着杂志文章的集合,希望找出发表文章最多的前3名作者。

    那么按照以下步骤创建管道:

    1:将每篇文章中的作者投射出来;

    2:将作者按照名称排序,统计出每个名字出现的次数;

    3:将作者按照名字出现的次数降序排列;

    4:将返回的结果限制为前3个。

    1:{"$project": {"author": 1}} --> {"_id": id, "author": authorName}

         通过"fieldName": 1选择需要投射的字段,默认会投射"_id"字段。

    2:{"$group": {"_id": "$author", "count": {"$sum": 1}}} --> {"_id": authorName, "count":articleCount}

         指明分组字段"author",该操作执行完后,每个作者只对应一个结果文档,所以"author"就成了文档的唯一标识符("_id")。

    3:{"$sort": {"$count": 1}}

    4:{"$limit": 3}
> db.article.aggregate(
... {"$project": {"author": 1}},
... {"$group": {"_id": "$author", "count": {"$sum": 1}}},
... {"$sort": {"$count": 1}},
... {"$limit": 3}
)
{
"result": [
{
"_id": "Bob",
"count": 490
},
{
"_id": "Ellice",
"count": 420
},
{
"_id": "Nora",
"count": 400
}
],
"ok": 1
}
如果管道没有给出预期的结果,可以按照需要进行调试,逐步添加管道操作符调试。

管道操作符

    $match

    $match:对于文档集合进行筛选,之后可以在筛选得到的文档子集上做聚合。

        注意:不能在"$match"上使用地理空间操作符。尽可能将$match放在管道的前面位置,这样做优点:过滤不需要的文档,以减少管道的工作量,在投射和分组之前执行$match,查询可以使用索引。

    $project

    $project:从文档中提取字段,还可以重命名字段,等做一些有意思的操作。
// 将每个用户文档的"_id"在返回结果中重命名成userId:
> db.article.aggregate(
... {"$project": {"userId": "$_id", "_id": 0}}
)
{
"result": [
{
"userId": ObjectId("54fd0571e4b055a0030461fb")
},
{
"userId": ObjectId("54fd0571e4b055a0030461fc")
},
...
],
"ok": 1
}
        注意:对字段进行重命名时,MongoDB并不会记录字段的历史名称。因此,如果在原字段名上有一个索引,被重命名之后,聚合框架之后就无法使用这个索引。因此应该尽量在修改字段名称之前使用索引。
// 聚合框架无法在sort操作中使用在originFieldName上创建的索引:
> db.article.aggregate(
... {"$project": {"newFieldName": "$originFieldName"}},
... {"$sort": {"newFieldName": 1}}
)

    1.管道表达式
        在集合框架中,有几个表达式可以用来组合或进行任意深度的嵌套,以便创建复杂的表达式。
    2.数学表达式
// 求总薪资:
    > db.employees.aggregate(
    ... {
    ...     "$project": {
    ...         "totalPay": {
    ...             "$add": ["$salary", $bonus]
    ...         }
    ...     }
    ... }
    )


操作符语法描述
$add[exp1[, exp2, ..., expN]]求和
$subtract[exp1, exp2]求差
$multiply[exp1[, exp2, ..., expN]]求积
$divide[exp1, exp2]求商
$mod[exp1, exp2]求余
    3.日期表达式
// 计算每个雇员在公司的工作时间:
> db.employees.aggregate(
... {
... "$project": {
... "tenure": {
... "$subtract": [{"$year": new Date()}}, {"$year": "$hireDate"}]
... }
... }
... }
)
      提取日期的表达式:"$year", "$month", "$week", "$dayOfMonth", "$dayOfWeek", "$dayOfYear", "$hour", "$minute", "$second"。

    4.字符串表达式
// 生成j.doe@example.com格式的email地址,提取firstName第一个字符,并拼接lastName生成指定格式:
> db.employees.aggregate(
... {
...     "$project": {
...         "email": {
...              "$concat": [
...                  {
...                      "$substr": ["$firstName", 0, 1],
...                      ".",
...                      "$lastName",
...                      "@example.com"
...                  }
...              ]
...         }
...     }
... }
)

操作符语法描述
$substr[expr, startOffset, numToReturn]截取字符串,从startOffset字节开始的numToReturn个字节
$concat[exp1[, exp2, ... , expN]]连接字符串
$toLowerexpr转换称小写
$toUpperexpr转换称大写
    5.逻辑表达式
// 为学生打分,出勤率占10%,日常测验占30%,期末考试占60%,(如果是老师最宠爱的学生,那么分数是100):
> db.students.aggregate(
... {
...     "$project": {
...         "grade": {
...              "$cond": [
...                  "$teacherPet",
...                  100, // if
...                  {    // else
...                      "$add": [
...                          "$multiply": [.1, "$attendanceAvg"],
...                          "$multiply": [.3, "$quizzAvg"],
...                          "$multiply": [.6, "$testAvg"]
...                      ]
...                  }
...              ]
...         }
...     }
... }
)

比较表达式
$cmp[exp1, exp2]相等返回0,exp1<exp2返回负数,否则返回正数
$strcasecmp[string1, string2]比较string1和string2,区分大小写,只对罗马字符有效
$eq/$ne/$gt/$gte/$le/$lte[exp1, exp2]进行比较操作,返回比较的结果(true/false)
布尔表达式
$and[exp1[, exp2, ... , expN]]与运算
$or[exp1[, exp2, ... , expN]]或运算
$notexpr取反
控制语句
$cond[booleanExpr, trueExpr, falseExpr]如果booleanExpr值为true,返回trueExpr,否则返回falseExpr
$ifNull[expr, replacementExpr]如果expr是null,返回replacementExpr,否则返回expr

   $group  

   $group:将文档依据特定字段的不同值进行分组。

    1.分组操作符
    对每一个分组进行计算,得到相应的结果。例:我们最先介绍的示例
    2.算术操作符
    $sum:value, 对于分组中的每一个文档,将value与计算结果相加。

    $avg:value, 返回每个分组的平均值。
    3.极值操作符
// 查找学生考试成绩的最高分和最低分:
> db.scores.aggregate(
... {
...     "$group": {
...         "_id": "$grade",
...         "lowestScore": {"$min": $score},
...         "highestScore": {"$max": $score}
...     }
... }
)
         如果数据是排过序的,那么$first和$last效率比$min和$max高,否则反之。

操作符语法描述
$maxexpr返回分组内的最大值
$minexpr返回分组内的最小值
$firstexpr返回分组的第一个值,忽略后面的所有值,只有排序之后(数据有序),才有意义
$lastexpr返回分组的最后一个值,忽略前面所有值,只有排序之后(数据有序),才有意义
    4.数组操作符
    $addToSet:expr, 如果当前数组中不包含expr,那么将其添加到数组中。返回结果集每个元素最多只出现一次,而且元素的顺序时不确定的。
    $push:expr, 不管expr是什么值,都将其添加到数组中,返回包含所有值的数组。

    $unwind

    $unwind:可以将数组中的每一个值拆分为单独的文档。
// 拆分博客评论为单个文档
> db.bolgs.aggregate(
... {"$unwind": "$comments"}
)
{
"result": [
{
// 博客公共部分 start
"_id": ObjectId("54fd0571e4b055a0030461fb"),
"author": "r",
"post": "hello world!",
// 博客公共部分 end
"comments": {
"author": "bob",
"content": "good post"
}
},
{
"_id": ObjectId("54fd0571e4b055a0030461fb"),
"author": "r",
"post": "hello world!",
"comments": {
"author": "bill",
"content": "nice post"
}
},
...
],
"ok": 1
}

    $sort

    $sort:可以根据任何字段(或者多个字段)进行排序。

    $limit

    $limit:接受一个数字n,返回结果集中的前n个文档。

    $skip

    $skip:接受一个数字n,丢弃结果集中的前n个文档,将剩余文档作为结果返回。

MongoDB 聚合管道
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: