MongoDB
是一个基于分布式文件存储的数据库。由C++
语言编写。旨在为WEB
应用提供可扩展的高性能数据存储解决方案。
1.前言
自从前后端分离后就一直有争辩,前后端分离的界限在哪里,前端仅仅是写页面吗?需要学习服务器数据库等后端的知识吗?
我认为是非常有必要的,假如就笼统以浏览器和服务器区分前后端,如何解释浏览器的 indexedDB
的存在。存在就是合理的,了解越多可以走得越远。
公司最近有 mongo
聚合的需求,我瞬间有了极大的兴趣,这篇文章是 mongo
的学习和解决问题笔记。
2.环境
环境安装与本篇文章的关系不大,但提供几个渠道:
3.介绍
在城市中有几所学校(db
),我们来查看下城市中的学校都有哪些

然后需要指定学校,作用是可以操作该学校(mongo
默认指向test
)

4.插入
学校(test
)到了期末考试,成绩如下:
姓名 | 性别 | 语文 | 数学 | 英语 | 参加考试时间 |
---|---|---|---|---|---|
小一 | 男生 | 90 | 90 | 90 | 2019年6月10号 |
小二 | 女生 | 80 | 80 | 80 | 2019年6月10号 |
小三 | 女生 | 70 | 70 | 70 | 2019年6月10号 |
小四 | 女生 | 60 | 60 | 60 | 2019年6月12号 |
成绩出来后老师把分数录入成绩系统。
// 插入的方法:insertOne插入一条数据
db.student.insertOne({
name: "小一",
sex: '男生',
chinese: '90',
math: '90',
english: '90',
createdAt: new Date('2016-06-09T16:00:00Z')
})

注意:
new Date('2016-06-09T16:00:00Z')
是世界标准时间,mongo
记录的也是世界标准时间。中国在东八区,时间加8小时,换算过来刚好是2019年6月10号。
老师觉得一个个添加太烦了,所以一次性添加:
// // 插入的方法:insertMany 插入一条数据
db.student.insertMany([
{ name: "小二", sex: '女生', chinese: '80', math: '80', english: '80', createdAt: new Date('2016-06-09T16:00:00Z')},
{ name: "小三", sex: '女生', chinese: '70', math: '70', english: '70', createdAt: new Date('2016-06-09T16:00:00Z')},
{ name: "小四", sex: '女生', chinese: '60', math: '60', english: '60', createdAt: new Date('2016-06-11T16:00:00Z')}
])

- 学校(
db
):数据库,mongo
可以容纳多个数据库,默认使用test
;- 学生集合(
student
):集合,可以理解为集合所有学生的班级;- 学生信息(
data
):文档,可以理解为单个学生信息。
5.修改
老师突然发现,小三其实是女装大佬,老师被骗恼羞成怒,于是修改其性别。
// 更新方法:updateOne
db.student.updateOne(
{ name: '小三' },
{ $set: { sex: '女装大佬' }}
)

其中
$set
是更新操作符,告诉mongo
需要对符合的数据进行什么操作。用法差不多,列举部分如下(更多更新操作符):
- $currentDate:设置时间;
- $inc:按指定的数量增加字段的值;
- $min:仅当指定的值小于现有字段值时才更新字段;
- $max:仅当指定的值大于现有字段值时才更新字段;
- $mul:将字段的值乘以指定的量;
- $rename:重命名字段;
- $set:设置文档中字段的值;
- $setOnInsert:如果更新导致文档插入,则设置字段的值。对修改现有文档的更新操作没有影响;
- $unset:从文档中删除指定的字段。
6.查找
三个学生都努力学习,经过期末考试完后,小一自信可以拿第一,于是小一去查自己的成绩。
// 查询方法:find
// pretty方法可以让数据更好看
db.student.find({name: '小一'}).pretty()

然后再查小二和小三的成绩。他突然发现小三是女装大佬的事实,震惊!
// 查询方法:find
db.student.find({$or: [{name: '小二'}, {name: '小三'}]}).pretty()

其中
$or
是查询操作符,告诉mongo
需要对符合的数据进行什么操作。用法差不多,列举部分如下(更多查询操作符):
7.删除
小一内心纠结,最后把这事告诉小三。小三觉得无颜面对同学和老师,决定退学。老师把小三的信息从教务系统删除。
// 删除方法:deleteOne
db.student.deleteOne({name: '小三'})

8.循环
期末老师做工作总结,发现成绩录入成字符串,应该是数字类型才对,所以操作循环更改。
// 遍历方法:forEach
db.student.find().forEach(function(doc) {
doc.chinese = NumberInt(doc.chinese);
doc.math = NumberInt(doc.math);
doc.english = NumberInt(doc.english);
db.student.save(doc);
})

数字类型:
NumberInt
强制转化为数字类型。
9.聚合
期末老师需要把班级的语文、数学、英语的平均分计算出来。
9.1 aggregate
// 聚合方法:aggregate
// 而$chinese、$math和$english只是代表文档字段。
db.student.aggregate([
{
$group: {
_id: null,
sumChinese: { $sum: '$chinese' },
avgChinese: { $avg: '$chinese' },
sumMath: { $sum: '$math' },
avgMath: { $avg: '$math' },
sumEnglish: { $sum: '$english' },
avgEnglish: { $avg: '$english' },
}
}
]).pretty()

$group
是分组操作符,根据_id
来进行分组,如果传null
就是不分组,选择所有的数据。 其中$sum
和$avg
是聚合操作符,告诉mongo
需要对符合的数据进行什么操作。用法差不多,列举部分如下(更多聚合操作符):
老师发现上面的计算方式是计算所有的学生的成绩,而这次只需要计算2019年6月10号考试成绩,修改如下:
db.student.aggregate([
{
$match: {
createdAt: {
$gte: new Date('2016-06-09T16:00:00Z'),
$lte: new Date('2016-06-10T16:00:00Z')
}
},
},
{
$project: {
name: 1,
sex: 1,
chinese: 1,
math: 1,
english: 1,
time: {$add: ['$createdAt', 8 * 60 * 60 * 1000]}
}
},
{
$group: {
_id: {
year: {'$year': '$time'},
month: {'$month': '$time'},
day: {'$dayOfMonth': '$time'},
},
sumChinese: { $sum: '$chinese' },
avgChinese: { $avg: '$chinese' },
sumMath: { $sum: '$math' },
avgMath: { $avg: '$math' },
sumEnglish: { $sum: '$english' },
avgEnglish: { $avg: '$english' },
}
}
]).pretty()

time
字段用来显示中国真实时间,它的值是 createedAt
的值再加8个小时。因为 mongo
默认存储世界标准时间,中国在东八区,需要加8个小时才能正确显示中国时间。
$match
:筛选操作符,顾名思义只有满足条件的数据才继续传递下去;$project
:修改返回数据结构的操作符,1表示显示,不写或者0则不显示。(aggregate
聚合方法接受数组参数,因为有管道概念,简单说前面命令执行完的结果作为后面命令的参数,像jquery
的点连接符。)
9.2 MapReduce
除了使用聚合方法,还可以使用 MapReduce
方法来计算。
Map-Reduce
是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。MongoDB提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。
db.student.mapReduce(
function(){
emit(null, this);
},
function(key, values){
var reducedVal = {
sumChinese: 0,
avgChinese: 0,
sumMath: 0,
avgMath: 0,
sumEnglish: 0,
avgEnglish: 0
};
for (var i = 0; i < values.length; i++) {
reducedVal.sumChinese += values[i].chinese;
reducedVal.sumMath += values[i].math;
reducedVal.sumEnglish += values[i].english;
}
reducedVal.avgChinese = reducedVal.sumChinese / values.length;
reducedVal.avgMath = reducedVal.sumMath / values.length;
reducedVal.avgEnglish = reducedVal.sumEnglish / values.length;
return reducedVal;
},
{
query: {createdAt: {$gte: new Date('2016-06-09T16:00:00Z'), $lte: new Date('2016-06-10T16:00:00Z')}},
out: 'sum'
}
).find().pretty()

mongo
语句的意思是:
- 根据
query
筛选出合适的数据(相当于聚合aggregate
的$match
),交给map
函数(mapReduce
接受的第一个参数); map
函数调用emit
函数遍历所有筛选数据,传递的第一个参数表示分组,我们不需要分组就传递null
,第二个参数是对应的数据,我们把完整数据传递下去给给reduce
函数(mapReduce
接受的第二个参数);reduce
函数接收map
传递的key
和values
值(就是对应上面的emit(null, this)
),我们只需要按自己的需求遍历组装,返回组装好的数据就可以了。
10.总结
mongo
很好玩,本篇文章只是记录功能开发中运用 mongo
的一些场景,而且 mongo
的内容远远不止本篇文章写的这么一点点范围,有兴趣可以查阅官方文档。