https://segmentfault.com/a/1190000010826809
什么是管道操作符(Aggregation Pipeline Operators)
mongoDB有4类操作符用于文档的操作,例如find查询里面会用到的$gte,$in等。操作符以$开头,分为查询操作符,更新操作符,管道操作符,查询修饰符4大类。其中管道操作符是用于聚合管道中的操作符。
管道操作符的分类
管道操作符可以分为三类:
- 阶段操作符(Stage Operators)
- 表达式操作符(Expression Operators)
- 累加器(Accumulators)
阶段操作符(Stage Operators)
阶段操作符是使用于db.collection.aggregate方法里面,数组参数中的第一层。
db.collection.aggregate( [ { 阶段操作符:表述 }, { 阶段操作符:表述 }, ... ] )
表达式操作符(Expression Operators)
表达式操作符主要用于在管道中构建表达式时使用,使用类似于函数那样需要参数,主要用于$project操作符中,用于构建表达式,使用方法一般如下:
方法1:
{ <operator>: [, ... ] }
方法2:
{ <operator>:}
累加器(Accumulators)
累加器本来只能使用与$groud下,但是版本3.2或以上,部分累加器还能使用于$project。当在$group中使用时,累加器是针对每个分组使用的;当在$project中使用时,累加器则是针对每个字面量起作用,具体用法下一篇文章阐述。
常用阶段操作符
操作符 | 简述 |
---|---|
$match | 匹配操作符,用于对文档集合进行筛选 |
$project | 投射操作符,用于重构每一个文档的字段,可以提取字段,重命名字段,甚至可以对原有字段进行操作后新增字段 |
$sort | 排序操作符,用于根据一个或多个字段对文档进行排序 |
$limit | 限制操作符,用于限制返回文档的数量 |
$skip | 跳过操作符,用于跳过指定数量的文档 |
$count | 统计操作符,用于统计文档的数量 |
$group | 分组操作符,用于对文档集合进行分组 |
$unwind | 拆分操作符,用于将数组中的每一个值拆分为单独的文档 |
$lookup | 连接操作符,用于连接同一个数据库中另一个集合,并获取指定的文档,类似于populate |
阶段操作符详解
假设有一个保存用户的集合Users,一个文章的集合Articles,数据大致如下:
users:
[ { name: 'John', age: 16, sex: male, city: guangzhou, _id: 1, ...}, { name: 'Rose', age: 18, sex: female, city: beijing, _id: 2, ...}, { name: 'Jack', age: 29, sex: male, city: guangzhou, _id: 3, ...}, { name: 'Allen', age: 18, sex: female, city: beijing, _id: 4, ...}, { name: 'Cruz', age: 22, sex: male, city: guangzhou, _id: 5, ...}, { name: 'Peter', age: 18, sex: male, city: guangzhou, _id: 6, ...}, { name: 'Kelly', age: 23, sex: female, city: shanghai, _id: 7, ...}, ... ]
articles:
[ { title: 'this is article A', author: 'John', _id: 1, ... }, { title: 'this is article B', author: 'Jack', _id: 2, ... }, { title: 'this is article C', author: 'Rose', _id: 3, ... }, { title: 'this is article D', author: 'John', _id: 4, ... }, { title: 'this is article E', author: 'John', _id: 5, ... }, ... ]
$match 匹配操作符
说明:
用于重构每一个文档的字段,可以提取字段,重命名字段,甚至可以对原有字段进行操作后新增字段
用法:
{ $match: {} }
示例:
- 查询用户年龄是18岁的用户
db.users.aggregate([{ $match : { age : "18" } }]);
$project 投射操作符
说明:
用于对文档集合进行筛选
用法:
{ $project: {} }
specification的规则
规则 | 描述 |
---|---|
<字段名>: 1 or true | 选择需要返回什么字段 |
_id: 0 or false | 不返回_id(默认返回) |
<字段名>: 表达式 | 使用表达式,可以用于重命名字段,或对其值进行操作,或新增字段 |
<字段名>: 0 or false | 选择需要不返回什么字段,注意:当使用这种用法时,就不要用上面的方法 |
示例1:
- 用户集合投射用户姓名
- 不返回_id
db.users.aggregate([{ $project : { name: 1 } }]);
示例2:
- 将_id重命名为userId
- 不返回_id_
db.users.aggregate([{ $project : { ueserId: '$_id', _id: 0 } }]);
示例3:
- 返回新字段username,并使用表达式让它的值为name的大写。
db.users.aggregate([ { $project : { name: 1, username: { $toUpper: '$name' }, _id: 0 } } ]);
关于管道表达式:最简单的“$project”表达式是包含和排除字段(如: { name: 1 }),以及字段名称$fieldname(如: { userId: '$_id' })。除此以外,还可以使用表达式操作符(如: $toUpper)构成更丰富的表达式,将多个字面量和变量组合在一起使用,得到更多有意思的值,更多表达式操作符的说明及使用在另外的篇章中详细阐述。
$sort 排序操作符
说明:
用于根据一个或多个字段对文档进行排序
用法:
{ $sort: {: , : ... } }
示例:
- users集合按照年龄age从低到高排序
db.users.aggregate([{ $sort : { age: 1 } }]);
$limit 限制操作符
说明:
用于限制返回文档的数量
用法:
{ $limit:}
示例:
- 返回5篇article
db.articles.aggregate({ $limit : 3 });
$skip 跳过操作符
说明:
用于跳过指定数量的文档
用法:
{ $skip:}
示例:
- 跳过1个文档
db.users.aggregate([{ $skip : 1 }]);
$count 统计操作符
说明:
用于统计文档的数量
用法:
{ $count: <string> }
string是统计之后输出统计结果的字段名
示例:
- 统计文章的总数,以totalArticle返回
db.articles.aggregate([{ totalArticle : 1 }]);
$group 分组操作符
说明:
用于对文档集合进行分组
用法:
{ $group: { _id:, : { : }, ... } }
_id是必须的,用作分组的依据条件
示例:
- 将用户(users)按性别(sex)分组
db.users.aggregate([{ $group : { _id: '$sex' } }]);
返回结果:
[ { _id: 'male' }, { _id: 'female' } ]
进阶示例:
- 将用户(users)按性别(sex)分组
- 分组后使用计算各自性别的平均年龄
- 统计不同的性别的人数,并以count返回
db.users.aggregate([ { $group : { _id: '$sex', avgAge: { $avg: '$age' }, conut: { $sum: 1 } } } ]);
返回结果:
[ { _id: 'male', avgAge: <男性平均年龄>, count: <男性人数> }, { _id: 'female', avgAge: <女性平均年龄>, count: <女性人数> } ]
此处用到的表达式 { $avg: '$age' } 用于求平均年龄,$avg是求均值的操作符,$sum用于汇总, 都只能在$group中使用的累加器,mongoDB3.2以上版本则还可以在$project中使用,详细会在另外的篇章中阐述。
$unwind 拆分操作符
说明:
用于将数组中的每一个值拆分为单独的文档
用法:
{ $unwind:}
3.2+版本的用法:
增加icludeArrayIndex,preserveNullAndEmptyArrays两个可选配置
{ $unwind: { path:, includeArrayIndex: <string>, preserveNullAndEmptyArrays: } }
字段 | 类型 | 描述 |
---|---|---|
path | string | 必填,数组的字段名,指定需要拆分的字段 |
includeArrayIndex | string | 可选,定义返回的字段名,返回的值是拆分前值在原数组的位置 |
preserveNullAndEmptyArrays | boolean | 可选,配置在path的值为空或缺失的情况下是否拆分, 默认false |
示例:
假设articles文档集合是这样:
{ title: 'this is article A', author: 'John', _id: 1, comments: ['a', 'b', 'c']}
db.articles.aggregate([{ $unwind: '$comments' }]);
结果:
[ { title: 'this is article A', author: 'John', _id: 1, comments: 'a'}, { title: 'this is article A', author: 'John', _id: 1, comments: 'b'}, { title: 'this is article A', author: 'John', _id: 1, comments: 'c'}, ]
进阶示例(v3.2+):
假设articles文档集合是这样:
[ { title: 'this is article A', author: 'John', _id: 1, comments: ['a', 'b', 'c'] } { title: 'this is article B', author: 'Jack', _id: 2 }, { title: 'this is article C', author: 'Amy', _id: 3, comments: [] }, { title: 'this is article D', author: 'Lam', _id: 4, comments: null }, ]
操作:
db.articles.aggregate([ { $unwind: { path: '$comments', includeArrayIndex: 'arrayIndex', } } ]);
结果:
[ { title: 'this is article A', author: 'John', _id: 1, comments: 'a', arrayIndex: NumberLong(0) }, { title: 'this is article A', author: 'John', _id: 1, comments: 'b', arrayIndex: NumberLong(1) }, { title: 'this is article A', author: 'John', _id: 1, comments: 'c', arrayIndex: NumberLong(2) }, ]
操作:
db.articles.aggregate([ { $unwind: { path: '$comments', preserveNullAndEmptyArrays: true, } } ]);
结果:
[ { title: 'this is article A', author: 'John', _id: 1, comments: 'a' }, { title: 'this is article A', author: 'John', _id: 1, comments: 'b' }, { title: 'this is article A', author: 'John', _id: 1, comments: 'c' }, { title: 'this is article B', author: 'Jack', _id: 2 }, { title: 'this is article C', author: 'Amy', _id: 3 }, { title: 'this is article C', author: 'Amy', _id: 3, comments: null } ]
$lookup 连接操作符
说明:
用于连接同一个数据库中另一个集合,并获取指定的文档,类似于populate
用法:
{ $lookup: { from:, localField: from the input documents>, foreignField: from the documents of the "from" collection>, as:
字段 | 描述 |
---|---|
from | 需要关联的集合名 |
localField | 本集合中需要查找的字段 |
foreignField | 另外一个集合中需要关联的字段 |
as | 输出的字段名 |
示例:
- ariticles中的author关联到user表
- authoer字段返回详细的用户的信息
db.articles.aggregate([ { $lookup: { from: "users", localField: "author", foreignField: "name", as: "author" } } ])
结果:
[ { title: 'this is article A', author: { name: 'John', age: 16, sex: male, city: guangzhou, _id: 1, ... }, _id: 1, ... }, { title: 'this is article B', author: { name: 'Jack', age: 29, sex: male, city: guangzhou, _id: 3, ... }, _id: 2, ... }, { title: 'this is article C', author: { name: 'Rose', age: 18, sex: male, city: beijing, _id: 2, ... }, _id: 3, ... }, { title: 'this is article D', author: { name: 'John', age: 16, sex: male, city: guangzhou, _id: 1, ... }, _id: 4, ... }, { title: 'this is article E', author: { name: 'John', age: 16, sex: male, city: guangzhou, _id: 1, ... }, _id: 5, ... }, ... ]
综合示例
需求
找出发表文章最多的5位作者,按发表文章排序,显示他的发表文章的总次数,和他自己的信息
- 文章按照作者分组,统计次数
- 按照次数从高到低排序
- 截取头5名
- 关联用户信息
- 不输出文章_id
操作
db.articles.aggregate([ { $group: { _id: "$author", count: { $sum: 1 }, } }, { $sort: { count: -1 } }, { $skip: 5 }, { $lookup: { from: "users", localField: "author", foreignField: "name", as: "author" } }, { $project: { _id: 0, } } ])
总结
本文介绍了几个使用聚合管道查询时常用的管道操作符的用法,熟练地综合使用以上操作符可以对数据进行多样的处理,组合,统计,得出多样化的数据。另外再加以配合表达式操作符(Expression Operators)组成的表达式, 或者在$project或$group中使用累加器(Accumulators)能查询统计的内容会更加的多样化。