Mongoose开发实战-高级篇
Aggregate
)。管道
和表达式
。管道
的概念,你可以跳过这一段话。如果你不了解,我在这里打个比方,比如生活中的水管,水(也就是我们的数据源)源源不断的从一节(一个管道)流向另一节(另一个管道);如果对某一节(一个管道)的水做了一些处理(也就是数据的筛选,排序等),那么在下一节(另一个管道)接收到的水就是你处理后的,当然,你也可以再次处理,如此反复...最后流到你家的就是经过层层处理的水(也就是我们需要得到的数据)。聚合函数
的原理就是这样,后一个管道得到的数据是上一个管道处理后的数据...。db.COLLECTION_NAME.aggregate(OPERATION, CALLBACK)
OPERATION: Object | Array,可选管道:
$project
:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。对应project()
方法$match
:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。对应match()
。$limit
:用来限制MongoDB聚合管道返回的文档数。对应limit()
方法$skip
:在聚合管道中跳过指定数量的文档,并返回余下的文档。对应skip()
。$unwind
:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。对应unwind()
方法$group
:将集合中的文档分组,可用于统计结果。对应group()
方法$sort
:将输入文档排序后输出。对应sort()
方法$geoNear
:输出接近某一地理位置的有序文档。对应near()
。
v3.2
$sample
:随机选择N个$lookup
:连接操作符,用于连接同一个数据库中另一个集合,并获取指定的文档,类似于populate
更多管道操作符:https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
对于$group
的表达式:
$sum
计算总和。$avg
计算平均值$min
获取集合中所有文档对应值得最小值。$max
获取集合中所有文档对应值得最大值。$push
在结果文档中插入值到一个数组中。$addToSet
在结果文档中插入值到一个数组中,但不创建副本。$first
根据资源文档的排序获取第一个文档数据。$last
根据资源文档的排序获取最后一个文档数据
更多表达式:https://docs.mongodb.com/manual/meta/aggregation-quick-reference/#aggregation-expressions
是不是看得眼花缭乱,还是用实例来学习吧!
完整实例:mongodb-pratice
首先往citys集合中插入如下数据:
{province: 'guangdong', city: 'guangzhou', person: 600, industry: [{name: 'IT', person: 200}, {name: 'teacher', person: 400}]}
{province: 'guangdong', city: 'shenzhen', person: 700, industry: [{name: 'IT', person: 300}, {name: 'teacher', person: 400}]}
{province: 'beijing', city: 'beijing', person: 600, industry: [{name: 'IT', person: 350}, {name: 'teacher', person: 250}]}
看看实例(1),查询人口超过1000的省份:
// modules/citys/citys.controller.js
exports.getCityGtThousand = (req, res) => {
CityModel.aggregate([
{$group: {_id: '$province', total: {$sum: '$person'}}},
{$match: {total: {$gt: 1000}}}
], (err, result) => {
...
})
}
在上面的代码中,$province
引用的是原文档中的province
,如果在前面的管道中有同名属性,则后续引用的是最后被赋值的,详情请参考实例(2)。
注意:使用$group
时,_id
是必须的,用作分组的依据条件。
结果如下:
[{"_id":"guangdong","total":1300}]
上面的聚合查询相当于SQL:
SELECT province, SUM(person) AS total FROM citys GROUP BY province HAVING total > 1000
其实上面的查询可分为两步(两个管道):
1. 将相同省份的城市的人口加起来,并命名为total,并且将province
属性赋值给_id
。
CityModel.aggregate([
{$group: {_id: '$province', total: {$sum: '$person'}}}
])
得到的结果是:
[{"_id":"beijing","total":600},{"_id":"guangdong","total":1300}]
2. 比较total,返回大于1000的数据
CityModel.aggregate([
{$group: {_id: '$province', total: {$sum: '$person'}}},
{$match: {total: {$gt: 1000}}}
])
最终结果就是:
[{"_id":"guangdong","total":1300}]
下面我们来看看实例(2),查询总IT人口大于400的省份且返回所有城市名称。
看着是不是有点复杂,其实当你像实例(1)一样拆分开来,你会发现很简单:
1. 计算每个省份的IT总人口
2. IT总人口大于400
3. 返回所有城市名称
看看代码:
// modules/citys/citys.controller.js
exports.getITPerson = (req, res) => {
CityModel.aggregate([
{$unwind: '$industry'},
{$match: {'industry.name': 'IT'}},
{
$group: {
_id: {province: '$province'}, itTotal: {$sum: '$industry.person'}, city: {$push: '$city'}
}
},
{$match: {itTotal: {$gt: 400}}}
]).exec((err, result) => {
...
})
}
我们依旧来分步看看上面的查询。
(1) $unwind
用来拆分数组:
CityModel.aggregate([
{$unwind: '$industry'}
])
查询结果:
[
{
"_id": "5a0e83e3aea7e7fba7ff0ab2",
"province": "guangdong",
"city": "guangzhou",
"person": 600,
"industry": {
"name": "IT",
"person": 200
}
},
{
"_id": "5a0e83e3aea7e7fba7ff0ab2",
"province": "guangdong",
"city": "guangzhou",
"person": 600,
"industry": {
"name": "teacher",
"person": 400
}
}
...
]
将industry
数组中每一项拆分开来,其他字段一一复制。
(2) $match
很简单,筛选匹配,类似find():
{$match: {'industry.name': 'IT'}}
筛选行业为IT的数据
(3) 接下来执行$group
:
$group: {
_id: {province: '$province'}, itTotal: {$sum: '$industry.person'}, city: {$push: '$city'}
}
根据省份province分组,将同一个省份的IT人口相加,同时将城市名称放到一个名为city的数组中。
(4) 最后再次筛选:
{$match: {itTotal: {$gt: 400}}}
返回总IT人口大于400的数据。
其他管道操作符详解
(1) 排序
语法:
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
1是正序,-1是反序
实例:以城市总人口排序,且值返回城市/省份/人数三个字段:
// modules/citys/citys.controller.js
CityModel.aggregate([
{ $sort: {person: 1} },
{
$project: {
_id: 0,
province: 1,
person: 1,
city: 1
}
}
])
注意:在Mongoose 3.4前,只有_id(默认显示)才可以指定为0或false,其他字段默认不进入下一个管道,可以设置为1或true进入下一个管道。(3.4版本可以将其他字段设为0或false)
(2) 限制和跳过
语法:
{ $limit: <positive integer> }
{ $skip: <positive integer> }
实例:从第二条数据开始,返回一条数据:
// modules/citys/citys.controller.js
CityModel.aggregate([
{$skip: 1},
{$limit: 1}
])
(3) 随机
语法:
{ $sample: { size: <positive integer> } }
实例:随机返回一条数据:
// modules/citys/citys.controller.js
CityModel.aggregate({$sample: {size: 1}})
注:Mongoose 3.2后才有
(4) 联表
在《Mongoose 开发实战:进阶篇》中,我们讲解了联表查询populate。在Mongoose 3.2后,我们可以更方便的连接表。
语法:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
参数说明:
- from 需要关联的集合名
- localField 本集合中需要查找的字段
- foreignField 另外一个集合中需要关联的字段
- as 输出的字段名
实例
我们往users集合里面插入三条数据:
{name: '张三', age: '22', sex: 'male' , phone: '13123123123', address: {
city: 'guangzhou'
}},
{name: '李四', age: '19', sex: 'male' , phone: '13123123123', address: {
city: 'beijing'
}},
{name: '王五', age: '25', sex: 'male' , phone: '13123123123', address: {
city: 'guangzhou'
}}
对应name名称,再往文章中插入三条数据:
{title: 'Mongoose 开发实战:基础篇', content: '讲解连接数据库,建文档等', author: '张三'},
{title: 'Mongoose 开发实战:进阶篇', content: '讲解建索引,添加验证器等', author: '李四'},
{title: 'Mongoose 开发实战:高级篇', content: '讲解聚合函数', author: '王五'}
现在要查询张三发表的文章:
// modules/users/users.controller.js
...
UsersModel.aggregate([
{
$lookup: {
from: 'articles',
localField: 'name',
foreignField: 'author',
as: 'userArticle'
}
}, {
$project: {
_id: 0,
name: 1,
'userArticle.title': 1,
'userArticle.author': 1
}
},
{
$match: {name: '张三'}
}
])
...
// [{"name":"张三","userArticle":[{"title":"Mongoose 开发实战:基础篇","author":"张三"}]}]
相关文章:
如有任何问题或疑问,欢迎在下方评论区留言!