mongodb知识点总结
发布于 2 年前 作者 yangqian 3569 次浏览 来自 分享

mongodb知识总结

微信云开发使用的云数据库集成了简化版的mongodb,因此学习两者之一就可以很快掌握另一个

mongoDB

开篇

<!--
1.什么是MongoDB?
MongoDB和MySQL一样都是数据库, 都是存储数据的仓库,
不同的是MySQL是关系型数据库, 而MongoDB是非关系型数据库

2.什么是非关系型数据库?
- 在'关系型数据库'中, 数据都是存储在表中的, 对存储的内容有严格的要求
  因为在创建表的时候我们就已经规定了表中有多少个字段,
                          已经规定了每个字段将来要存储什么类型数据,
                          已经规定了每个字段将来是否可以为空,是否必须唯一等等

- 在'非关系型数据库'中, 没有表概念, 所以存储数据更加灵活
  因为不需要创建表,所以也没有规定有哪些字段,
                       也没有规定每个字段数据类型,
                       也没有规定每个字段将来是否可以为空,是否必须唯一等等

- '关系型数据库'由于操作的都是结构化的数据, 所以我们需要使用结构化语言SQL来操作
- '非关系型数据库'由于数据没有严格的结构要求, 所以无需使用SQL来操作

3.什么是MongoDB?
存储文档(BSON)的非关系型数据库
-->
<!--
例如在MySQL中:
|--------------------------------------------------------|
|   name(varchar(255) not null)   |    age(int unique)   |
|--------------------------------------------------------|
我们可以把            'zs', 33        保存到表中
但是我们不能将         33, 'zs'       保存到表中
但我们不能能将         null, 33       保存到表中
但是我们不能将         'zs', 33,  '男' 保存到表中
但是我们不能再次将     'zs', 33        保存到表中
-->
<!--
例如在MongoDB中:
我们可以把         {name: 'zs', age: 33};              保存到集合中
我们也可以把       {name: 33, age: 'zs'};              保存到集合中
我们也可以把       {name: null, age: 33};              保存到集合中
我们也可以把       {name: 'zs', age: 33, gender:'男'}; 保存到集合中
但是我们可以再次将 {name: 'zs', age: 33};              保存到集合中

- '非关系型数据库'可以看做是'关系型数据库'的功能阉割版本,
  通过减少用不到或很少用的功能,从而提升数据库的性能
-->
<!--
4.MongoDB是如何存储文档的?
MySQL中所有的数据都是存储在表中的, 而MongoDB中所有的数据都是存储在集合中的
4.1MySQL

                |--行1
        |--表1--|--行2
数据库--|       |--行3
        |--表2
        |--... ...

4.2MongoDB

                  |--文档1
        |--集合1--|--文档2
数据库--|         |--文档3
        |--集合2
        |--... ...
-->
<!--
5.企业开发如何选择?
- 关系型数据库和非关系型数据库之间并不是替代关系, 而是互补关系
所以在企业开发中大部分情况是结合在一起使用.
- 对于数据模型比较简单、数据性能要求较高、数据灵活性较强的数据, 我们存储到非关系型数据库中
相反则存储到关系型数据库中
- 具体使用: 会在项目中实现
-->

快速上手

<!--
https://docs.mongodb.com/manual/
https://www.mongodb.org.cn/tutorial/
1.连接MongoDB服务器
通过mongo连接MongoDB服务器

2.查看数据库
show dbs
#和MySQL中的 show databases; 指令一样

3.创建数据库
use 数据库名称
#和MySQL中的 use 指令一样, 只不过MongoDB中的use数据库不存在会自动创建

4.查看数据库中有哪些集合
show collections
#和MySQL中的 show tables; 指令一样

5.创建集合
db.createCollection('集合名称');
#和MySQL中的 create table xxx(); 指令一样

6.插入数据
db.集合名称.insert(文档对象);
#和MySQL中的 insert into xxx values () 指令一样

7.查询数据
db.集合名称.find();
#和MySQL中的 select * from xxx; 指令一样

8.删除集合
db.集合名称.drop()
#和MySQL中的 drop table xxx; 指令一样

9.删除数据库
db.dropDatabase()
#在哪个数据库中就会删除哪个数据库
#和MySQL中的 drop database xxx; 指令一样

10.和MySQL的不同
- 没有MySQL中表的概念, 取而代之的是集合
- 创建集合时不用指定集合中有哪些字段
- 只要是一个合法的文档对象都可以往里面存储
- ... ...
-->

主键

<!--
1.主键
- MongoDB的主键和MySQL一样, 也是用于保证每一条数据唯一性的
- 和MySQL不同的是, MongoDB中的主键无需明确指定
    + 每一个文档被添加到集合之后, MongoDB都会自动添加主键
    + MongoDB中文档主键的名称叫做 _id

- 默认情况下文档主键是一个ObjectId类型的数据
    + ObjectId类型是一个12个字节字符串(5e8c5ae9-c9d35e-759b-d6847d)
        + 4字节是存储这条数据的时间戳
        + 3字节的存储这条数据的那台电脑的标识符
        + 2字节的存储这条数据的MongoDB进程id
        + 3字节是计数器

2.为什么要使用ObjectId类型数据作为主键?
因为MongoDB是支持'横向扩展'的数据库
- 横向扩展是指'增加数据库服务器的台数'
- 纵向扩展是指'增加数据库库服务器的配置'

- 过去一个数据库只能安装在一台电脑上, 但是每台电脑的性能是有峰值的
  一旦达到峰值就会导致服务器卡顿、宕机、重启等问题.
  所以过去为了防止如上问题的出现,我们只能不断的'纵向扩展'
  也就是不断的提升服务器的配置, 让服务器能处理更多的请求
  但是纵向扩展也是有峰值的, 一台电脑的配置不可能无限提升
  所以为了解决这个问题就有了分布式数据库
- 分布式数据库是指可以在多台电脑上安装数据库, 然后把多台电脑组合成一个完整的数据库,
  在分布式数据库中,我们可以通过不断同步的方式, 让多台电脑都保存相同的内容
  当用户请求数据时, 我们可以把请求派发给不同的数据库服务器处理
  当某一台服务器宕机后, 我们还可以继续使用其它服务器处理请求
  从而有效的解决了单台电脑性能峰值和单台电脑宕机后服务器不能使用的问题

2.2为什么要使用ObjectId类型数据作为主键?
正是因为MongoDB是一个分布式数据库, 正是因为分布式数据库可以把请求派发给不同的服务器
所以第一次插入数据时, 我们可能派发给了A服务器, 插入到了A服务器的数据库中
但是第二次插入数据时, 我们又可能派发给了B服务器, 插入到了B服务器的数据库中
但是B服务器此时并不知道A服务器当前的主键值是多少, 如果通过MySQL中简单的递增来保证数据的唯一性
那么将来在多台服务器同步数据的时候就会出现重复的情况, 所以MongoDB的主键并没有使用简单的递增
而是使用了ObjectId类型数据作为主键

3.是否支持其它类型数据作为主键?
3.1在MongoDB中支持除了'数组类型'以外的其它类型数据作为主键
3.2在MongoDB中甚至还支持将一个文档作为另一个文档的主键(复合主键)

db.person.insert({name: 'lnj', age: 33});
db.person.insert({_id: 1, name: 'lnj', age: 33});
#db.person.insert({_id: 1, name: 'lnj', age: 33}); #报错
db.person.insert({_id: '1', name: 'lnj', age: 33});
db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33});
#db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33}); #报错
db.person.insert({_id: {gender: '男', name:'it66'}, name: 'lnj', age: 33});
-->

创建文档

<!--
1.主键
- MongoDB的主键和MySQL一样, 也是用于保证每一条数据唯一性的
- 和MySQL不同的是, MongoDB中的主键无需明确指定
    + 每一个文档被添加到集合之后, MongoDB都会自动添加主键
    + MongoDB中文档主键的名称叫做 _id

- 默认情况下文档主键是一个ObjectId类型的数据
    + ObjectId类型是一个12个字节字符串(5e8c5ae9-c9d35e-759b-d6847d)
        + 4字节是存储这条数据的时间戳
        + 3字节的存储这条数据的那台电脑的标识符
        + 2字节的存储这条数据的MongoDB进程id
        + 3字节是计数器

2.为什么要使用ObjectId类型数据作为主键?
因为MongoDB是支持'横向扩展'的数据库
- 横向扩展是指'增加数据库服务器的台数'
- 纵向扩展是指'增加数据库库服务器的配置'

- 过去一个数据库只能安装在一台电脑上, 但是每台电脑的性能是有峰值的
  一旦达到峰值就会导致服务器卡顿、宕机、重启等问题.
  所以过去为了防止如上问题的出现,我们只能不断的'纵向扩展'
  也就是不断的提升服务器的配置, 让服务器能处理更多的请求
  但是纵向扩展也是有峰值的, 一台电脑的配置不可能无限提升
  所以为了解决这个问题就有了分布式数据库
- 分布式数据库是指可以在多台电脑上安装数据库, 然后把多台电脑组合成一个完整的数据库,
  在分布式数据库中,我们可以通过不断同步的方式, 让多台电脑都保存相同的内容
  当用户请求数据时, 我们可以把请求派发给不同的数据库服务器处理
  当某一台服务器宕机后, 我们还可以继续使用其它服务器处理请求
  从而有效的解决了单台电脑性能峰值和单台电脑宕机后服务器不能使用的问题

2.2为什么要使用ObjectId类型数据作为主键?
正是因为MongoDB是一个分布式数据库, 正是因为分布式数据库可以把请求派发给不同的服务器
所以第一次插入数据时, 我们可能派发给了A服务器, 插入到了A服务器的数据库中
但是第二次插入数据时, 我们又可能派发给了B服务器, 插入到了B服务器的数据库中
但是B服务器此时并不知道A服务器当前的主键值是多少, 如果通过MySQL中简单的递增来保证数据的唯一性
那么将来在多台服务器同步数据的时候就会出现重复的情况, 所以MongoDB的主键并没有使用简单的递增
而是使用了ObjectId类型数据作为主键

3.是否支持其它类型数据作为主键?
3.1在MongoDB中支持除了'数组类型'以外的其它类型数据作为主键
3.2在MongoDB中甚至还支持将一个文档作为另一个文档的主键(复合主键)

db.person.insert({name: 'lnj', age: 33});
db.person.insert({_id: 1, name: 'lnj', age: 33});
#db.person.insert({_id: 1, name: 'lnj', age: 33}); #报错
db.person.insert({_id: '1', name: 'lnj', age: 33});
db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33});
#db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33}); #报错
db.person.insert({_id: {gender: '男', name:'it66'}, name: 'lnj', age: 33});
-->

<!--
1.写入一个文档
db.<collection>.insertOne(
    <document>,
    {
        writeConcern: <document>
    }
);
document: 需要写入的文档
writeConcern: 写入安全级别

2.安全级别
用于判断数据是否写入成功,
安全级别越高, 丢失数据风险越小, 但是性能消耗(操作延迟)也就越大
默认情况下MongoDB会开启默认的安全些级别,先不用关心

3.注意点
在使用insertXXX写入文档时, 如果调用insertOne的集合不存在会自动创建

db.person.insertOne({name:'zs', age:18})
db.person.find()
db.student.insertOne({name:'zs', age:18}) #集合不存在会自动创建
db.student.find()

4.其它方式
db.<collection>.save(
    <document>,
    {
        writeConcern: <document>
    }
);

db.person.save({name:'ls', age:19})
db.person.find()
db.teacher.save({name:'ls', age:19}) #集合不存在会自动创建
db.teacher.find()

5.insertOne和save不同
主键冲突时insertOne会报错,而save会直接用新值覆盖久值
db.person.insertOne({_id:1, name:'ww', age:22})
db.person.find()
db.person.insertOne({_id:1, name:'ww', age:22}) #报错
db.person.save({_id:1, name:'it666', age:66}) #用新数据替换久数据
db.person.find()
-->
<!--
1.写入多个文档
db.<collection>.insertMany(
    [<document>, ...],
    {
        writeConcern: <document>,
        ordered: <boolean>
    }
);
ordered: 是否按顺序写入
ordered默认取值是true, 也就是会严格按照顺序写入
如果ordered是false, 则不会按照顺序写入, 但写入效率更高(系统会自动优化)

db.person.insertMany(
[{name:'zs', age:18},{name:'ls', age:19},{name:'ww', age:20}],
{}
)
db.person.find()
2.注意点:
如果ordered是true, 前面的文档出错, 后面的所有文档都不会被写入
如果ordered是false, 前面的文档出错, 后面的所有文档也会被写入

db.person.insertMany(
[{_id:1, name:'zs', age:18},{_id:1, name:'ls', age:19},{_id:2, name:'ww', age:20}],
{ ordered: true }
)
db.person.find()

db.person.remove({})
db.person.insertMany(
[{_id:1, name:'zs', age:18},{_id:1, name:'ls', age:19},{_id:2, name:'ww', age:20}],
{ ordered: false }
)
db.person.find()
-->
<!--
1.写入一个或多个文档
db.<collection>.insert(
    <document> or ,[<document>, ...]
    {
        writeConcern: <document>,
        ordered: <boolean>
    }
);
insertOne和insertMany结合体

2.注意点:
和insertOne/insertMany一样,  集合不存在会自动创建
和insertOne/insertMany一样,  主键冲突会报错
和insertMany一样, 默认都是按顺序插入, 前面的文档出错, 后续所有文档不会被插入
-->

读取文档

查询文档

<!--
1.查询文档
db.<collection>.find(
    <query>,
    <projection>
)
query: 查询条件, 相当于MySQL中的where
projection: 投影条件, 规定了结果集中显示那些字段, 相当于MySQL中的 select 字段1, 字段2, .. from 表名;

2.查询所有文档
db.<collection>.find();
不传入条件, 默认就是查询所有

3.查询满足条件文档
db.person.insert([{name:'zs', age:17},{name:'ls', age:18},{name:'ww', age:19}])
3.1单个字段条件
db.person.find() // 默认会返回指定集合中所有的数据和所以的字段
db.person.find({name:'zs'}) // 我们可以通过第一个参数指定查询条件, find方法会把所有满足条件的数据返回给我们
3.2多个字段条件
db.person.find({name:'zs', age:17}) // 默认是And关系, 也就是默认要求同时满足所有指定的条件, 才会返回对应的数据
db.person.find({age:17, name:'zs'}) // 注意点: 没有顺序要求, 只要是同时满足多个条件即可
3.3文档中又是文档情况
db.person.insert(
[{name:'zs', age:17, book:{name:'HTML', price:66}},
{name:'ls', age:18, book:{name:'JavaScript', price:88}},
{name:'ww', age:19, book:{name:'Vue', price:199}}]
)
db.person.find({'book.name':'JavaScript'}) // 如果某一个文档的某一个字段的取值又是一个文档, 那么在判断的时候我们可以通过'字段.文档属性名称'的方式来判断

4.查询指定字段
0表示不显示, 1表示显示
除主键以外, 其它字段不能同时出现0和1(要么不写,写了就必须全是1或者全是0)
db.person.find({},{_id:0}) // 如果不想查询某一个字段, 那么就可以指定这个字段的投影取值为0
db.person.find({},{_id:0, name:1, age:1, book:1}) // 如果想查询某一个字段, 那么就可以指定这个字段的投影取值为1
                                                  // 默认情况下如果不指定, 那么所有字段的投影取值都是1
db.person.find({},{_id:0, name:1, age:1, book:0}) // 除了_id字段以外, 其它的字段不能同时出现0和1
db.person.find({},{_id:0, book:0})
-->

比较运算符

<!--
1.比较操作符
和MySQL一样, MongodDB中也支持很多比较操作符
$eq: 等于 / $ne: 不等于
$gt: 大于 / $gte: 大于等于
$lt: 小于 / $lte: 小于等于

2.使用格式
db.<collection>.find(
    {<field>: {$<operator>: <value>}},
    <projection>
)

3.示例
db.person.insert([{name:'zs', age:17, gender:'男'},{name:'ls', age:18},{name:'ww', age:19}])
查询名称叫做zs的人
查询所有成年人
查询所有未成年人
查询所有不是18岁的人
db.person.find({name:'zs'}) //默认情况下就是按照相等来判断
db.person.find({name:{$eq:'zs'}}) //这里就是明确的告诉MongoDB需要按照相等来查询
db.person.find({age:{$gte: 18}})
db.person.find({age:{$lt: 18}})
db.person.find({age:{$ne: 18}})
注意点: 没有指定字段也算作不等于
db.person.find({gender:{$ne: '女'}}) // 注意点: 在做不等于判断的时候, 没有需要判断的字段, 也算作是不等于
-->
<!--
1.其它比较操作符
$in: 匹配和任意指定值相等的文档
$nin:匹配和任意指定值都不相等的文档

2.使用格式
db.<collection>.find(
    {<field>: {$<operator>: [<value1>, <value2>, ...]}},
    <projection>
)

3.实例
查询名称叫做zs或者ls的人
查询名称不叫zs或者ls的人
查询性别不是男或女的人
db.person.find({name:{$in:['zs', 'ls']}}) // 匹配和任意指定值相等的文档
db.person.find({name:{$nin:['zs', 'ls']}}) // 匹配和任意指定值都不相等的文档
db.person.find({gender:{$nin:['男', '女']}}) // 注意点: 和$ne一样, 如果没有需要判断的字段, 也算作满足条件
注意点: 没有指定字段也算作不包含
-->

逻辑操作符

<!--
1.逻辑操作符
$not: 匹配条件不成立的文档
{<field>: {$not: {<expression>}}}
$and: 匹配条件全部成立的文档
{<field>: {$and: [{<expression1>}, {<expression2>}, ...}]}
$or : 匹配至少一个条件成立的文档
{<field>: {$or: [{<expression1>}, {<expression2>}, ...}]}
$nor: 匹配多个条件全部不成立的文档
{<field>: {$nor: [{<expression1>}, {<expression2>}, ...}]}

2.示例:
//2.1$not
//查询所有年龄不等于18岁的人
db.person.find({age:{$ne:18}})
db.person.find({age:{$not:{$eq:18}}})
//查询不是男人的人
db.person.find({gender:{$eq:'男'}})
// 注意点: $not运算符和$ne/$nin一样, 如果需要查询的字段不存在, 也会算作条件成立
db.person.find({gender:{$not:{$eq:'男'}}})

//2.2$and
//查询所有名称叫做zs的未成年人
db.person.find({$and:[{name:{$eq:'zs'}},{age:{$lt:18}}]})
db.person.find({$and:[{name:'zs'},{age:{$lt:18}}]})
db.person.find({name:'zs', age:{$lt:18}})

//2.3$or
//查询所有名称叫做zs或者ls的人
db.person.find({name:{$in:['zs','ls']}})
db.person.find({$or:[{name:{$eq:'zs'}},{name:{$eq:'ls'}}]})
db.person.find({$or:[{name:'zs'},{name:'ls'}]})

//2.4$nor
//查询所有名称不叫zs或者ls的人
db.person.find({name:{$nin:['zs','ls']}})
db.person.find({$nor:[{name:'zs'},{name:'ls'}]})

//查询所有名称不叫zs或者性别不是男的人
// 注意点: $nor运算符和$ne/$nin/$not一样, 如果需要查询的字段不存在, 也会算作条件成立
db.person.find({$nor:[{gender:'男'}]})
-->

字段操作符

<!--
1.字段操作符
$exists: 查询包含某个字段的文档
{<field>: {$exists: <boolean>}}
$type:   查询指定字段包含指定类型的文档
{<field>: {$type: <BSON> or [<BSON1>, <BSON2>]}}

2.查询包含字段gender的人
db.person.insert([
{name:'zs', age:17, gender:'男'},
{name:'ls', age:18},
{name:'ww', age:19},
{name:'it666', age:20, gender:'女'}
])
// 需求: 要求查询出所有拥有gender属性的文档
db.person.find({gender:{$exists: true}})

3.应用场景:
// 应用场景: 配合$ne/$nin/$nor/$not来清理数据
db.person.find({gender:{$ne:'男'}})
db.person.find({gender:{$ne:'男', $exists:true}})

4.查询所有年龄是字符串类型的文档
db.person.insert([
{name:'itzb', age:'666'},
{name:'lnj', age:'888'},
])
// 需求: 要求查询出所有age属性的取值是字符串类型的文档
db.person.find({age:{$type:'string'}})
-->

数组操作符

<!--
1.数组操作符
$all      : 匹配数组中包含所有指定查询值的文档
{<field>: {$all: [<value1>, <value2>, ...]}}
$elemMatch: 匹配数组中至少有一个能完全匹配所有的查询条件的文档
{<field>: {$elemMatch: {<query1>, <query2>, ...}}}

2.示例
查询tags中同时拥有html和js的文档
db.person.insert([
{name: 'zs', tags:['html', 'js', 'vue']},
{name: 'ls', tags:['html', 'react', 'vue']},
{name: 'ww', tags:['html', 'node', 'js']},
])
db.person.find({tags:{$all:['html', 'js']}})

查询所有名称叫做zs,年龄是18岁的文档
db.school.insert([
{class: 'one',
students: [
    {name:'zs', age: 18},
    {name:'ls', age: 19},
    {name:'ww', age: 20},
]},
{class: 'two',
students: [
    {name:'zs', age: 20},
    {name:'ls', age: 19},
    {name:'ww', age: 18},
]},
])

db.school.find({'studnets.name':'ww', 'studnets.age':18})
db.school.find({studnets:{$elemMatch:{name:'ww',age:18}}})
-->

运算操作符:正则表达式

<!--
1.运算操作符
{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: /pattern/<options> } }
查询满足正则的文档

2.示例
db.person.insert([
{name:'zs', age:18},
{name:'ls', age:19},
{name:'ww', age:17},
{name:'Zsf', age:18},
{name:'Lnj', age:19},
{name:'Wz', age:17}
])

// 需求: 要求查询出所有姓z的人(文档)
db.person.find({name:{$regex:/^z/, $options: 'i'}})
// 需求: 要求查询出所有姓是z或者l的人(文档)
db.person.find({name:{$in:[/^z/i, /^l/i]}})
-->

文档游标

<!--
1.文档游标
1.1为什么学习前端都要学习MongoDB?
因为MongoDB原生就支持JavaScript, 也就是我们可以直接在MongoDB中混入JS代码
1.2什么是文档游标
我们执行find方法后, find方法其实是有返回值的, find方法会返回一个文档游标(相当于C语言指针)
1.3文档游标常用方法
hasNext(): 是否还有下一个文档
next():    取出下一个文档
forEach(): 依次取出所有文档
1.4文档游标注意点
默认情况下通过文档游标遍历完所有文档后, 系统会在10分钟后自动关闭当前游标
如果不想自动关闭, 我们可以通过noCursorTimeout函数来保持游标一直有效
let cursor = db.person.find().noCursorTimeout()
如果想手动关闭游标, 我们也可以通过close函数来手动关闭游标
cursor.close()
2.示例
// 需求: 往person集合中插入100个文档
var arr =[];
for(var i = 0; i < 100; i++){
  arr.push({name:'it'+i, age:18+i});
}
db.person.insertMany(arr)

var cursor = db.person.find().noCursorTimeout()
//cursor[0]
//cursor[1]
while(cursor.hasNext()){
   printjson(cursor.next())
}

cursor.forEach(printjson)
cursor.close()
-->

分页方法

<!--
1.分页方法
cursor.limit(<number>): 取多少个文档
cursor.skip(<offset>) : 跳过多少个文档

2.示例
//var cursor = db.person.find()
// 需求: 要求取出前5个文档
//cursor.limit(5)
// 需求: 要求跳过前面的5个文档, 取出剩余的所有
//cursor.skip(5)
// 注意点: 我们可以直接在find方法后面调用limit方法或者skip方法
//db.person.find().limit(5)
//db.person.find().skip(5)

3.分页函数注意点
// 注意点: MongoDB是支持链式调用的
// 需求: 跳过前面5个文档, 取出后面的5个文档
//db.person.find().skip(5).limit(5)
// 注意点:在链式调用的时候, 无论skip写在前面还是后面, 都会在limit之前执行
db.person.find().limit(5).skip(10)
-->

排序函数

<!--
1.排序函数
cursor.sort({field: ordering, ...}): 按照指定规则排序
ordering为1表示升序排序
ordering为-1表示降序排序

2.示例
// 注意点: 默认情况下find方法只会返回100个文档
db.person.find()
db.person.insert({name:'itzb', age:15})
db.person.find().limit(101)
db.person.find().sort({age:1})
db.person.find().sort({age:-1})

3.注意点
3.1find方法默认只会取出100个文档
3.2sort函数永远在分页函数之前执行
db.person.find().skip(5).limit(5)
db.person.find().skip(5).limit(5).sort({age:-1})
-->

统计函数

<!--
1.统计函数
cursor.count(<applySkipLimit>): 统计集合中文档的个数
applySkipLimit默认为false, 表示忽略skip和limit

2.示例
db.person.find().count()
// 注意点: count函数可以接收一个applySkipLimit参数, 通过这个参数可以告诉MongoDB在统计的时候是否需要忽略Skip和Limit
//      默认情况下applySkipLimit的取值是false, 表示忽略Skip和Limit
db.person.find().skip(6).count()
db.person.find().limit(5).count()
db.person.find().skip(6).count({applySkipLimit:true})
db.person.find().limit(5).count({applySkipLimit:true})

2.统计函数注意点
在find函数不提供筛选条件时, count函数会从集合的元数据中取得结果
在单台电脑上是这个结果是准确的,
但是如果数据库为分布式结构(多台电脑)时,
如果不给find函数提供筛选条件, 那么count函数返回的结果并不一定准确
-->

更新文档

save方法

<!--
1.更新文档
MongoDB中有三个常用的更新方法: save()/update()/findAndmodify()

2.save方法
save用于往集合里添加一个新文档或者覆盖文档
当没有指定文档_id的时候就是新增
当指定了集合中已经存在的_id的时候就是覆盖

3.示例
db.person.insert([
{name:'zs', age:18},
{name:'ls', age:19},
{name:'ww', age:20},
{name:'zs', age:21},
])
-->

update方法

<!--
1.update方法
db.collection.update(<filter>, <update>, <options>)
<filter>: 筛选条件
<update>: 新的内容
<options>: 额外配置

2.通过update覆盖满足条件数据
默认情况下如果<update>没有使用更新操作符, 那么就会使用指定的内容覆盖符合条件的内容

3.示例:
db.person.update({name:'lnj'}, {name:'zs'})

4.注意点:
// 注意点: update方法默认情况下就是覆盖
//      如果不想覆盖, 而是想单纯的更新, 那么就必须在第二个参数中使用'更新操作符'
db.person.update({name:'ww'},{score: 99.9},{})
// 注意点: update方法默认只会更新满足条件的第一个文档
//      如果想更新所有满足条件的文档, 那么就必须指定第三个参数
db.person.update({name:'zs'}, {name:'zs', age:55}, {})
// 注意点: 如果在使用update方法的时候, 在第二个参数中指定了_id, 那么就必须保证指定的_id和被更新的文档的_id的取值一致
//      否则就无法更新, 否则就会报错
// 开发技巧: 在企业开发中如果需要使用update方法, 那么就不要指定_id
db.person.update({name:'zs'}, {_id:1, name:'zs', age:55}, {})
db.person.update({name:'zs'}, {_id:ObjectId("5e9007350718cb6e37ab4515"), name:'zs', age:88}, {})

// 注意点: 如果想更新所有满足条件的文档, 我们可以指定第三个参数的取值multi:true
// 注意点: 如果指定了multi:true, 那么就必须在第二个参数中使用'更新操作符'
db.person.update({name:'zs'}, {name:'zs', age:55}, {multi:true})
-->

$set更新操作符

<!--
1.更新操作符
默认情况下update会使用新文档覆盖旧文档
如果不想覆盖而是仅仅想更新其中的某些字段
那么我们就需要使用update的更新操作符

2.$set更新操作符
$set: 更新或者新增字段, 字段存在就是更新, 字段不存在就是新增
格式: {$set:<value1>, ...}

3.示例:
db.person.update({name:'zs'}, {$set:{name:'itzb'}})
db.person.update({name:'itzb'}, {$set:{age:'888'}})

4.更新内嵌文档和数组
db.person.insert([
{name:'zs', age:18},
{name:'ls', age:19},
{name:'ww', age:20},
{name:'zs', age:21},
])
db.person.update({name:'ww'}, {age:55})
// 更新普通字段
db.person.update({name:'ls'}, {$set:{age:55}})
db.person.update({name:'zs'}, {$set:{age:88}}, {multi:true})

db.person.insert(
{
name:'ww',
age:18,
book:{name:'跟着江哥学编程', price:2888},
tags:['html', 'JavaScript']}
)
// 更新文档字段
db.person.update({name:'ww'}, {$set:{'book.name': 'it666.com'}})
// 更新数组字段
db.person.update({name:'ww'}, {$set: {'tags.0': 'vue'}})

5.注意点:
// 注意点: 如果操作的字段存在, 那么就是更新, 如果操作的字段不存在, 那么就是新增
db.person.update({name:'ls'}, {$set:{score: 59.5}})
// 注意点: 如果操作的是数组字段, 如果操作索引不存在, 那么也会自动新增
//      如果被操作的索引前面没有数据, 那么会自动用null来填充
db.person.update({name:'ww'}, {$set: {'tags.2': 'react'}})
db.person.update({name:'ww'}, {$set: {'tags.5': 'node'}})
-->

$unset更新操作符

<!--
1.$unset更新操作符
$unset: 删除字段
格式  :{$unset:{<field>:'', ...}}

2.示例:
// 删除普通字段
db.person.update({name:'ls'}, {$unset:{score:''}})
// 注意点: 如果使用$unset删除某一个字段, 那么后面赋值为任何的内容都不重要
db.person.update({name:'ls'}, {$unset:{age:'www.it666.com'}})

// 删除文档字段中的字段
db.person.update({name:'ww'}, {$unset:{'book.price': ''}})
// 删除数组字段中的元素
// 注意点: 如果删除的是数组字段中的元素, 那么并不会修改数组的长度, 而是用null来填充删除的内容
db.person.update({name:'ww'}, {$unset:{'tags.1': ''}})

3.注意点:
3.1删除数组元素并不会影响数组的长度, 而是设置为Null
3.2如果删除的字段不存在, 不会做任何操作
-->

$rename重命名操作

<!--
1.$rename更新操作符
$rename: 重命名字段
格式  :{$rename:{<field>:<newName>, ...}}

2.示例
db.person.update({name:'zs'}, {$rename:{name:'MyName'}})
// 注意点: 如果修改的是文档字段中的字段, 那么取值必须写上层级关系
db.person.update({name:'ww'}, {$rename:{'book.name':'book.BookName'}})
// 注意点: 如果要操作的字段不存在, 那么不会做任何的操作
db.person.update({name:'ls'}, {$rename:{age:'MyAge'}})
// 注意点: 如果重命名之后的名称已经存在了, 那么已经存在的字段就会被删除
// 底层的本质: 先调用了$unset删除了原有的book字段, 然后再调用$set修改字段的名称
db.person.update({name:'ww'}, {$rename:{name:'book'}})
// 注意点: 不能通过$rename更新操作符来操作数组
db.person.insert(
{
name:'it666',
age:666,
book:{name:'知播渔', price:999},
tags:[{name:'html', price:'123'}, {name:'js', price:456}]
}
)
db.person.update({name:'it666'}, {$rename:{'tags.0.name':'tags.0.TagName'}})

4.乾坤大挪移
// 可以将外层的字段转移到内层的文档中
db.person.update({name:'it666'}, {$rename:{age:'book.age'}})
db.person.find()
// 可以将内存文档中的字段, 转移到外层文档中
db.person.update({name:'it666'}, {$rename:{'book.age':'age'}})
-->

$inc$mul更新操作符

<!--
1.$inc和$mul更新操作符
$inc:更新字段值(增加或者减少字段保存的值)
格式: {$inc:{<field>: <number>}}
$mul:更新字段值(乘以或者除以字段保存的值)
格式: {$mul:{<field>: <number>}}

2.示例
db.person.update({name:'lnj'}, {$inc:{age:2}})
db.person.update({name:'lnj'}, {$inc:{age:-5}})

db.person.update({name:'lnj'}, {$mul:{age:0.5}})
db.person.update({name:'lnj'}, {$mul:{age:2}})

3.注意点:
3.1只能操作数字类型字段
3.2如果操作的字段不存在, 会自动新增这个字段
   不同的是$inc会把操作的值赋值给新增的字段, 而$mul会自动赋值为0
db.person.update({name:'lnj'}, {$inc:{weight:2}})
db.person.update({name:'lnj'}, {$mul:{height:2}})
-->

$max$min更新操作符

<!--
1.$min和$max更新操作符
$min:比较保留更小字段值
格式: {$min:{<field>: <value>}}
$max:比较保留更大字段值
格式: {$max:{<field>: <value>}}

2.示例
db.person.insert({name:'lnj', age:33})
db.person.update({name:'lnj'}, {$min:{age:50}})
db.person.update({name:'lnj'}, {$min:{age:18}})

db.person.update({name:'lnj'}, {$max:{age:5}})
db.person.update({name:'lnj'}, {$max:{age:55}})

3.注意点:
// 注意点: 如果操作的字段不存在, 那么会自动增加, 并且会将操作的值赋值给新增的字段
db.person.update({name:'lnj'}, {$min:{weight:120}})
db.person.update({name:'lnj'}, {$max:{height:175}})
// 注意点: 和$inc/$mul不同, $min/$max不仅仅能操作数值类型的字段, 只要是可以比较的字段都可以操作
db.person.insert({name:'def', age:666})
db.person.update({name:'def'}, {$min:{name:'efg'}})
db.person.update({name:'def'}, {$min:{name:'cde'}})
// 注意点: 不是相同的数据类型也可以进行比较
db.person.update({name:'lnj'}, {$min:{age:null}})

MongoDB对BSON的数据类型有一个潜在的排序规则
Null
Numbers(ints, longs, doubles, decimals)
Symbol, String
Object
Array
BinData
ObjectId
Boolean
Date
Timestamp
Regular Expression
-->

$addToSet数组更新操作符

<!--
1.$addToSet数组更新操作符
$addToSet: 向数组字段中添加元素
格式     : {$addToSet: {<field>:<values>, ...}}

2.示例
db.person.insert([
{name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js']},
{name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['vue', 'node']}
])
db.person.update({name:'zs'}, {$addToSet:{tags:'react'}})

3.注意点
// 注意点:如果操作的元素不存在, 那么会自动新增, 并且将操作的值赋值给新增的数组字段
db.person.update({name:'zs'}, {$addToSet:{other:'123'}})
// 注意点: $addToSet会自动去重, 如果添加的元素已经存在了, 那么就不会添加了
db.person.update({name:'zs'}, {$addToSet:{other:'123'}})
// 注意点: 如果往数组字段中添加的是文档类型, 那么必须一模一样才会去重
db.person.update({name:'zs'}, {$addToSet:{books:{name:'html', price:66}}})
db.person.update({name:'zs'}, {$addToSet:{books:{price:66, name:'html'}}})
// 注意点: 如果往数组字段中添加的是数组, 那么也必须一模一样才会去重
db.person.update({name:'ls'}, {$addToSet:{tags:['1', '2']}})
db.person.update({name:'ls'}, {$addToSet:{tags:['1', '2']}})
db.person.update({name:'ls'}, {$addToSet:{tags:['2', '1']}})
// 注意点: 如果往往数组字段中添加的是数组, 那么默认情况下会将整个数组作为一个元素添加进去
//      如果不想诶一个整体添加进去,那么必须使用$each来添加
db.person.update({name:'ls'}, {$addToSet:{tags:{$each: ['1', '2', '3']}}})
-->	

$push更新操作符

<!--
1.$push数组更新操作符
$push: 向数组字段中添加元素(不去重)
格式     : {$push: {<field>:<value1>, ...}}
-->
<!--
1.$pop数组更新操作符
$pop: 从数组字段中删除元素
格式: {$pop: {<field>:<1|-1>, ...}}

2.示例
db.person.update({name:'zs'}, {$pop:{tags:1}}) #删除最后一个
db.person.update({name:'zs'}, {$pop:{tags:-1}})#删除第一个

3.注意点
数组中的元素都被删除以后, 仍然会保留空的数组
-->

$pull更新操作符

<!--
1.$pull数组更新操作符
$pull: 从数组字段中删除特定元素
格式: {$pull: {<field>:<value|condition>, ...}}

2.示例
db.person.insert([
{name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js', ['1', '2']]},
{name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['a', 'b', 'ab', 'c', 'ac']}
])
删除特定元素
根据条件删除元素
db.person.update({name:'zs'}, {$pull:{tags:'js'}})
db.person.update({name:'ls'}, {$pull:{tags:/^a/}})

3.注意点
// 注意点: 如果要删除的元素是一个数组, 那么必须一模一样才能删除
db.person.update({name:'zs'}, {$pull:{tags:['2', '1']}})
db.person.update({name:'zs'}, {$pull:{tags:['1', '2']}})
// 注意点: 如果要删除的元素是一个文档, 那么不用一模一样也可以删除
db.person.update({name:'zs'}, {$pull:{books:{price:66, name:'html'}}})
db.person.update({name:'zs'}, {$pull:{books:{name:'js'}}})
-->	

$pullAll更新操作符

<!--
1.$pullAll数组更新操作符
$pullAll: 从数组字段中批量删除特定元素
格式: {$pullAll: {<field>: [<value1>, <value2>, ...], ...}}

2.示例
db.person.insert([
{name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js', ['1', '2']]},
{name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['a', 'b', 'ab', 'c', 'ac']}
])
db.person.update({name:'zs'}, {$pullAll:{tags:['html', 'js']}})

3.注意点
// 注意点: 和$pull一样, 如果删除的是数字字段中的数组元素, 那么必须一模一样才能删除
db.person.update({name:'zs'}, {$pullAll:{tags:[['2','1']]}})
db.person.update({name:'zs'}, {$pullAll:{tags:[['1','2']]}})
// 注意点: 和$pull不一样, 如果删除的是数组字段中的文档元素, 那么也必须一模一样才能删除
db.person.update({name:'zs'}, {$pullAll:{books:[{price:66,name:'html'}]}})
db.person.update({name:'zs'}, {$pullAll:{books:[{name:'html',price:66}]}})
-->

$和$[]更新操作符

<!--
1.$和$[]数组更新操作符
$   : 更新数组中满足条件的特定元素
格式:
db.<collection>.update(
{ <array field>:<query selector> }
{ <update operator>: {'<array field>.$':value}}
)
$[]: 更新数组中所有元素
db.<collection>.update(
{ <update operator>: {'<array field>.$[]':value}}
)

2.示例
db.person.insert([
{name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js', ['1', '2']]},
{name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['a', 'b', 'ab', 'c', 'ac']}
])
db.person.find()

db.person.update(
{name:'zs', tags:'js'},
{$set:{'tags.$':'JavaScript'}}
)

db.person.update(
{name:'zs'},
{$set:{'tags.$[]': 'it666'}}
)
-->

删除文档

<!--
1.删除文档
db.<collection>.remove(<query>, <options>)
<query>: 删除筛选条件
<options>: 删除额外配置

2.示例
db.person.insert([
{name:'zs', age:18},
{name:'zs', age:19},
{name:'ls', age:20},
{name:'ls', age:21},
{name:'ww', age:22},
{name:'zl', age:23},
])

2.示例
//2.1删除所有满足条件
// 注意点: 和update方法不同, remove方法默认就会删除所有满足条件的数据
db.person.remove({name:'zs'})
//2.2删除第一个满足条件
db.person.remove({name:'ls'},{justOne:true})
//2.3删除所有文档
db.person.remove({})
-->

聚合操作

$project

<!--
1.什么是聚合操作?
- 聚合操作就是通过一个方法完成一系列的操作
- 在聚合操作中, 每一个操作我们称之为一个阶段,
  聚合操作会将上一个阶段处理结果传给下一个阶段继续处理,
  所有阶段都处理完毕会返回一个新的结果集给我们

2.聚合操作格式
db.<collection>.aggregate(<pipeline>, <options>)
<pipeline>: 定义每个阶段操作
<options> : 聚合操作额外配置

3.聚合管道阶段
$project: 对输入文档进行再次投影
作用    : 按照我们需要的格式生成结果集
格式    : {$project:{<field>:<value>}}

4.示例
db.person.insert([
    {name:{firstName:'Jonathan', lastName:'Lee'}, age:18, book:{name:'玩转HTML', price: 88}},
    {name:{firstName:'Amelie', lastName:'Jiang'}, age:17, book:{name:'玩转JavaScript', price: 99}}
])
db.person.aggregate([
    {
        $project:{
            _id:0,
            clientName: '$name.firstName',
            clientAge: '$age'
        }
    }
])

5.聚合表达式
5.1字段路径表达式
$<filed>: 使用$来指示字段路径
$<filed>.<sub-field>: 使用$和.来指示内嵌文档字段路径
5.2字段路径表达式示例
$name
$book.name

6.注意点:
// 注意点: $project修改的是结果集而不是原有的集合
db.person.find()
// 注意点: 如果字段表达式使用了不存在的字段, 那么会自动用Null填充
db.person.aggregate([
    {
        $project:{
            _id:0,
            fullName: ['$name.firstName', '$name.middleName','$name.lastName'],
            clientAge: '$age'
        }
    }
])
-->	

$match

<!--
1.聚合管道阶段
$match: 和find方法中的第一个参数一样, 用于筛选符合条件的文档
格式  : {$match:{<query>}}

2.示例
db.person.aggregate([
    {
        $match:{
            'name.firstName':'Jonathan'
        }
    }
])
db.person.aggregate([
    {
        $match:{
            'name.firstName':'Jonathan'
        }
    },
    {
        $project:{
            _id:0,
            clientName: '$name.firstName',
            clientAge: '$age'
        }
    }
])

3.使用技巧:
应该在聚合操作的最前面使用$match, 这样可以有效减少处理文档的数量, 大大提升处理的效率
-->

$limit和$skip

<!--
1.聚合管道阶段
$limit: 和游标的limit方法一样, 用于指定获取几个文档
格式  : {$limit:<number>}
$skip : 和游标的skip方法一样, 用于指定跳过几个文档
格式  : {$skip:<number>}

2.示例
db.person.aggregate([
    {
        $skip:1
    },
    {
        $limit:1
    },
    {
        $project:{
            _id:0,
            clientName: '$name.firstName',
            clientAge: '$age'
        }
    }
])

-->

$unwind

<!--
1.聚合管道阶段
$unwind: 展开数组字段
格式   : {$unwind:{path:<field>}}

2.示例:
db.person.update({'name.firstName':'Jonathan'}, {$set:{tags:['html', 'js']}})
db.person.update({'name.firstName':'Amelie'}, {$set:{tags:'vue'}})
db.person.aggregate([
    {
        $unwind:{
            path:'$tags'
        }
    }
])

3.注意点:
3.1$unwind会为数组中的每个元素创建一个新的文档
3.2可以通过includeArrayIndex属性添加展开之后的元素在原数组中的位置
db.person.aggregate([
    {
        $unwind:{
            path:'$tags',
            includeArrayIndex: 'index'
        }
    }
])
3.3如果需要展开的字段不存在, 或者数组中没有元素, 或者为null, 会被unwind剔除
db.person.insert([
    {name:{firstName:'san', lastName:'zhang'}, age:20},
    {name:{firstName:'si', lastName:'li'}, age:21, tags:[]},
    {name:{firstName:'wu', lastName:'wang'}, age:22, tags:null}
])
3.4如果想让unwind不剔除不存在/没有元素/为Null的文档, 那么可以添加preserveNullAndEmptyArrays属性
db.person.aggregate([
    {
        $unwind:{
            path:'$tags',
            includeArrayIndex: 'index',
            preserveNullAndEmptyArrays: true
        }
    }
])
-->

$sort

<!--
1.聚合管道阶段
$sort: 和文档游标sort方法一样, 对文档进行排序
格式   : {$sort:{<field>>:1|-1}}

2.示例
db.person.aggregate([
    {
        $sort:{
            age: 1
        }
    }
])
-->

$lookup

<!--
1.聚合管道阶段
$lookup: 用来做关联查询
格式   :
{$lookup:{
    from: 关联集合名称,
    localField: 当前集合中的字段名称,
    foreignField:关联集合中的字段名称,
    as: 输出字段的名称
}}

2.示例:
db.person.insert([
    {name:{firstName:'Jonathan', lastName:'Lee'}, age:18, books:['html', 'js']},
    {name:{firstName:'Amelie', lastName:'Jiang'}, age:19, books:['vue']},
    {name:{firstName:'si', lastName:'Li'}, age:20, books:[]}
])
db.books.insert([
    {name:'html', price:88},
    {name:'js', price:99},
    {name:'vue', price:110},
])

db.person.aggregate([
    {
        $lookup:{
            from: 'books',
            localField: 'books',
            foreignField: 'name',
            as: 'booksData'
        }
    }
])

3.和unwind阶段结合使用
可以有效的过滤掉无效数据
可以给每个匹配的结果生成一个新的文档
db.person.aggregate([
    {
        $unwind:{
            path:'$books'
        }
    },
    {
        $lookup:{
            from: 'books',
            localField: 'books',
            foreignField: 'name',
            as: 'booksData'
        }
    }
])
-->	

$lookup相关查询

<!--
1.聚合管道阶段
$lookup: 用来做关联查询
格式   :
{$lookup:{
    from: 关联集合名称,
    let: {定义给关联集合的聚合操作使用的当前集合的常量},
    pipeline: [关联集合的聚合操作]
    as: 输出字段的名称
}}

2.示例:
不相关查询
db.person.aggregate([
    {
        $lookup:{
            from: 'books',
            pipeline: [
                {
                    $match:{
                        price:{$gte:100}
                    }
                }
            ],
            as: 'booksData'
        }
    }
])
相关查询
db.person.aggregate([
    {
        $lookup:{
            from: 'books',
            let: { bks: '$books'},
            pipeline: [
                {
                    $match:{
                        $expr:{
                            $and:[
                            {$gte: ['$price', 100]},
                            {$in: ['$name', '$$bks']}
                            ]
                        }
                        //price:{$gte:100}
                    }
                }
            ],
            as: 'booksData'
        }
    }
])

3系统变量表达式
$$<variable>: 使用$$来指示系统变量
-->

$group

<!--
1.聚合管道阶段
$group: 对文档进行分组
格式  :
{$group:{
    _id:<expression>,
    <field1>: {<accumulator1>: <expression1>}
    ... ...
}}
_id: 定义分组规则
<field>: 定义新字段

2.示例
db.person.insert([
{name:'zs', age:10, city:'北京'},
{name:'ls', age:20, city:'上海'},
{name:'ww', age:30, city:'北京'},
{name:'zl', age:40, city:'上海'},
{name:'lnj', age:50, city:'北京'},
{name:'jjj', age:60, city:'广州'},
])
db.person.aggregate([
    {$group:{
        _id:'$city',
        totalAge:{$sum:'$age'},
        avgAge:{$avg:'$age'},
        minAge:{$min:'$age'},
        maxAge:{$max:'$age'},
        totalName:{$push:'$name'}
    }}
])
-->

$out

<!--
1.聚合管道阶段
$out: 前面阶段处理完的文档写入一个新的集合
格式: {$out: <new collection name>}

2.示例:
db.person.aggregate([
    {
        $group:{
            _id: '$city',
            totalAge: {$sum:'$age'},
            avgAge: {$avg: '$age'},
            minAge: {$min: '$age'},
            maxAge: {$max: '$age'},
            totalAges: {$push: '$age'}
        }
    },
    {
        $out:'newPerson'
    }
])
db.newPerson.find()

3.注意点:
如果利用$out写入一个已经存在的集合, 那么集合中的原有数据会被覆盖
-->

配置

<!--
1.聚合操作额外配置
db.<collection>.aggregate(<pipeline>, <options>)
格式: {allowDiskUse: <boolean>}
allowDiskUse默认取值是false, 默认情况下管道阶段占用的内存不能超过100M,如果超出100M就会报错
如果需要处理的数据比较多, 聚合操作使用的内存可能超过100M, 那么我们可以将allowDiskUse设置为true
如果allowDiskUse设置为true, 那么一旦超出100M就会将操作的数据写入到临时文件中, 然后再继续操作
-->
<!--
1.字段路径表达式
$<filed>: 使用$来指示字段路径
$<filed>.<sub-field>: 使用$和.来指示内嵌文档字段路径
2.示例
$name
$book.name

3.系统变量表达式
$$CURRENT: 表示当前操作的文档
4.示例
$$CURRENT.name  等价于 $name

5.常量表达式
$literal:<value> : 表示常量<value>
6.示例
$literal:'$name' : 表示常量字符串$name

db.person.insert([
    {name:{firstName:'Jonathan', lastName:'Lee'}, age:18},
    {name:{firstName:'Amelie', lastName:'Jiang'}, age:19}
])
db.person.find()

db.person.aggregate([
    {$project:{
        _id:0,
        //myName:'$name.firstName', // 字段路径表达式
        //myAge:'$age' // 字段路径表达式
        //myName:'$$CURRENT.name.firstName', //系统变量表达式
        //myAge:'$$CURRENT.age' // 系统变量表达式
        myName:'$name.firstName',
        myAge:{$literal:'$age'} // 常量表达式
    }}
])

-->

数据类型转换

<!--
1.数据类型转换操作符
- MongoDB对于文档的格式并没有强制性的要求, 同一个集合中存储的文档, 字段的个数和数据类型都可以不同
对与文档的格式没有强制性的要求是MongoDB的一大优势, 但是同时也增加了数据消费端的使用难度
因为我们在使用数据的时候, 有可能同一个字段取出来的数据类型是不同的, 这样非常不利于我们后续操作
所以也正是因为如此, MongoDB在4.0中推出了$convert数据类型转换操作符
- 通过$convert数据类型转换操作符, 我们可以将不同的数据类型转换成相同的数据类型,
以便于后续我们在使用数据的过程中能够统一对数据进行处理

2.$convert格式
{$convert:{
    input: '需要转换的字段',
    to: '转换之后的数据类型',
    onError: '不支持的转换类型',
    onNull: '没有需要转换的数据'
}}

3.示例
db.person.insert([
{name:'zs', timestamp:ISODate('2020-08-09T11:23:34.733Z')},
{name:'ls', timestamp:'2021-02-14 12:00:06 +0800  '},
{name:'ww', timestamp:'  2023-04-01T12:00:00Z'},
{name:'zl', timestamp:'1587009270000'},
{name:'it666', timestamp:'Sunday'},
{name:'itzb'},
])
db.person.aggregate([
    {$project:{
        _id:0,
        timestamp:{
            $convert:{
                input:'$timestamp',
                to:'date',
                onError: '不支持的转换类型',
                onNull: '没有需要转换的数据'
            }
        }
    }}
])
-->
1 回复

mangoDB确实好香,最近已经在打算对我的vue+node+mysql的项目数据库方面进行重构了,换成mangoDB

回到顶部