mongodb可以当查询手册使用

第一章 MongoDB开篇

1.1 什么是MongoDB?
MongoDB和MySQL一样都是数据库, 都是存储数据的仓库,
不同的是MySQL是关系型数据库, 而MongoDB是非关系型数据库,MongoDB中的存储的数据是类JSON。
1.2 什么是非关系型数据库?
1.在'关系型数据库'中, 数据都是存储在表中的, 对存储的内容有严格的要求,因为在创建表的时候我们就
  	已经规定了表中有多少个字段,
  	已经规定了每个字段将来要存储什么类型数据,
  	已经规定了每个字段将来是否可以为空,是否必须唯一等等
2.在'非关系型数据库'中, 没有表概念, 所以存储数据更加灵活因为不需要创建表,所以
	也没有规定有哪些字段,
    也没有规定每个字段数据类型,
    也没有规定每个字段将来是否可以为空,是否必须唯一等等
3.'关系型数据库'由于操作的都是结构化的数据, 所以我们需要使用结构化语言SQL来操作
4.'非关系型数据库'由于数据没有严格的结构要求, 所以无需使用SQL来操作
1.3 什么是MongoDB?
1.存储文档(BSON)的非关系型数据库
    1)例如在MySQL中:
    |--------------------------------------------------------|
    |   name(varchar(255) not null)   |    age(int unique)   |
    |--------------------------------------------------------|
    我们可以把            'zs', 33        保存到表中
    但是我们不能将         33, 'zs'       保存到表中
    但我们不能能将         null, 33       保存到表中
    但是我们不能将         'zs', 33,  '男' 保存到表中
    但是我们不能再次将     'zs', 33        保存到表中
2)例如在MongoDB中:
    我们可以把         {name: 'zs', age: 33};              保存到集合中
    我们也可以把       {name: 33, age: 'zs'};              保存到集合中
    我们也可以把       {name: null, age: 33};              保存到集合中
    我们也可以把       {name: 'zs', age: 33, gender:'男'}; 保存到集合中
    但是我们可以再次将 {name: 'zs', age: 33};              保存到集合中
2.'非关系型数据库'可以看做是'关系型数据库'的功能阉割版本,
  通过减少用不到或很少用的功能,从而提升数据库的性能    
1.4 MongoDB是如何存储文档的?
1.MySQL中所有的数据都是存储在表中的, 而MongoDB中所有的数据都是存储在集合中的
	1)MySQL
                         |--行1
                |--表1-- |--行2
        数据库--|         |--行3
                |--表2
                |--... ...
    2)MongoDB
                          |--文档1
                |--集合1--|--文档2
        数据库--|          |--文档3
                |--集合2
                |--... ...

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

比较重要的数据,存储在mysql中,不是那么重要的数据,存储在MongoDB中。

第二章 MongoDB安装

2.1 安装MongoDB
1.https://www.mongodb.org/dl/win32
2.MongoDB版本号: 偶数为稳定版(推荐), 基数为开发版
3.MongoDB对32位操作系统支持不佳, 不推荐在32位系统上使用, 并且官方已经停更32位安装包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Za37Ikwj-1661331341467)(assets\image-20210704131248388.png)]

2.2 安装步骤
1.全程下一步,修改安装路径
2.配置环境变量, 将MongoDB的bin目录配置到系统变量Path中
3.可以去服务列表中打开服务,相当于开始服务器。
2.3 连接服务器
1.mongo

第三章 快速上手MongoDB

3.1 连接MongoDB服务器
1.通过mongo连接MongoDB服务器
3.2 查看数据库
1.show dbs
2.#和MySQL中的 show databases; 指令一样
3.3 创建数据库
1.use 数据库名称
2.#和MySQL中的 use 指令一样, 只不过MongoDB中的use数据库不存在会自动创建

user person;  创建数据库  数据库名是person;

如果没有向person数据库中插入集合,此时,这个数据库还是不存在的。
3.4 查看数据库中有哪些集合
1.show collections
2.#和MySQL中的 show tables; 指令一样
3.5 创建集合
前面,使用use person,此时,db就表示person。
1.db.createCollection('集合名称');
	 db.createCollection('users')
	 只要向person数据库中插入了集合,数据就才正式创建。
2.#和MySQL中的 create table xxx(); 指令一样
3.6 插入数据
1.db.集合名称.insert(文档对象);
	db.users.insert({name:"wc",age:100})
	插入时,它会自动给每一个文档,添加一个_id,_id叫主键。
2.#和MySQL中的 insert into xxx values () 指令一样
3.7 查询数据
1.db.集合名称.find();
	db.users.find();
2.#和MySQL中的 select * from xxx; 指令一样
3.8 删除集合
1.db.集合名称.drop()
	 db.users.drop();
	 如果数据库中没有一个集合,那么数据库也不存在了
2.#和MySQL中的 drop table xxx; 指令一样
3.9 删除数据库
1.db.dropDatabase()
	
2.#在哪个数据库中就会删除哪个数据库
3.#和MySQL中的 drop database xxx; 指令一样
3.10 和MySQL的不同
1.没有MySQL中表的概念, 取而代之的是集合
2.创建集合时不用指定集合中有哪些字段
3.只要是一个合法的文档对象都可以往里面存储
  ... ...
3.11 主键
1.MongoDB的主键和MySQL一样, 也是用于保证每一条数据唯一性的
2.和MySQL不同的是, MongoDB中的主键无需明确指定
   	1)每一个文档被添加到集合之后, MongoDB都会自动添加主键
    2)MongoDB中文档主键的名称叫做 _id

3.默认情况下文档主键是一个ObjectId类型的数据
    1)ObjectId类型是一个12个字节字符串(5e8c5ae9-c9d35e-759b-d6847d)
        a)4字节是存储这条数据的时间戳
        b)3字节的存储这条数据的那台电脑的标识符
        c)2字节的存储这条数据的MongoDB进程id
        d)3字节是计数器
3.12 为什么要使用ObjectId类型数据作为主键?
1.因为MongoDB是支持'横向扩展'的数据库
	1)横向扩展是指'增加数据库服务器的台数'
	2)纵向扩展是指'增加数据库库服务器的配置'
	3)过去一个数据库只能安装在一台电脑上, 但是每台电脑的性能是有峰值的
      一旦达到峰值就会导致服务器卡顿、宕机、重启等问题.
      所以过去为了防止如上问题的出现,我们只能不断的'纵向扩展'
      也就是不断的提升服务器的配置, 让服务器能处理更多的请求
      但是纵向扩展也是有峰值的, 一台电脑的配置不可能无限提升
      所以为了解决这个问题就有了分布式数据库
	4)分布式数据库是指可以在多台电脑上安装数据库, 然后把多台电脑组合成一个完整的数据库,
      在分布式数据库中,我们可以通过不断同步的方式, 让多台电脑都保存相同的内容
      当用户请求数据时, 我们可以把请求派发给不同的数据库服务器处理
      当某一台服务器宕机后, 我们还可以继续使用其它服务器处理请求
      从而有效的解决了单台电脑性能峰值和单台电脑宕机后服务器不能使用的问题
2.为什么要使用ObjectId类型数据作为主键?
    正是因为MongoDB是一个分布式数据库, 正是因为分布式数据库可以把请求派发给不同的服务器
    所以第一次插入数据时, 我们可能派发给了A服务器, 插入到了A服务器的数据库中
    但是第二次插入数据时, 我们又可能派发给了B服务器, 插入到了B服务器的数据库中
    但是B服务器此时并不知道A服务器当前的主键值是多少, 如果通过MySQL中简单的递增来保证数据的唯一性
    那么将来在多台服务器同步数据的时候就会出现重复的情况, 所以MongoDB的主键并没有使用简单的递增
    而是使用了ObjectId类型数据作为主键
3.13 是否支持其它类型数据作为主键?
1.在MongoDB中支持除了'数组类型'以外的其它类型数据作为主键
2.在MongoDB中甚至还支持将一个文档作为另一个文档的主键(复合主键)
3.	db.person.insert({name: 'wc', age: 33});
    db.person.insert({_id: 1, name: 'wc', age: 33});
    #db.person.insert({_id: 1, name: 'wc', age: 33}); #报错
    db.person.insert({_id: '1', name: 'wc', age: 33});
    db.person.insert({_id: {name:'xq', gender: '男'}, name: 'wc', age: 23});
    #db.person.insert({_id: {name:'xq', gender: '男'}, name: 'wc', age: 23}); #报错
    db.person.insert({_id: {gender: '男', name:'xq'}, name: 'wc', age: 23});
3.14 可视化工具
mongoDBManager

下载地址:https://www.mongodbmanager.com/

傻瓜式安装

第四章 MongoDB-创建文档

4.4 写入一个文档
1.	db.<collection>.insertOne(
    <document>,
       {
                writeConcern: <document>
            }
        );
    document: 需要写入的文档
    writeConcern: 写入安全级别
2.安全级别
    1)用于判断数据是否写入成功,
    2)安全级别越高, 丢失数据风险越小, 但是性能消耗(操作延迟)也就越大
    3)默认情况下MongoDB会开启默认的安全些级别,先不用关心
3.注意点
	在使用insertXXX写入文档时, 如果调用insertOne的集合不存在会自动创建
    db.users.insertOne({name:'z3',age:18});
    db.users.find()
    // student集合并不存在
    db.student.insertOne({name:'zs', age:18}) #集合不存在会自动创建
    db.student.find()
4.其它方式
	db.<collection>.save(
    <document>,
        {
            writeConcern: <document>
        }
    );
    // users集合现在是存在的
    db.users.save({name:'ls', age:19})
    db.person.find()
    // teacher集合不存在  不存在时,也会创建
    db.teacher.save({name:'ls', age:19}) #集合不存在会自动创建
    db.teacher.find()
5.insertOne和save不同
    主键冲突时insertOne会报错,而save会直接用新值覆盖旧值
    // users集合现在是存在的  自己指定主键
    db.users.insertOne({_id:1, name:'ww', age:22})
    db.users.find()
    db.users.insertOne({_id:1, name:'ww', age:22}) #报错
    // teacher集合存在
    db.teacher.save({_id:1, name:'wc', age:66}) #用新数据替换久数据
    db.teacher.find()
4.5 写入多个文档
1.	db.<collection>.insertMany(
    [<document>, ...],
        {
            writeConcern: <document>,
            ordered: <boolean>
        }
    );
    ordered: 是否按顺序写入
    ordered默认取值是true, 也就是会严格按照顺序写入
    如果ordered是false, 则不会按照顺序写入, 但写入效率更高(系统会自动优化)
    
    删除person数据库下面的users集合。
    
    向users集合中插入N个文档,没有这个集合,会自动创建:
    db.users.insertMany(
        [{name:'zs', age:18},{name:'ls', age:19},{name:'ww', age:20}],
        {}
    )
	db.person.find()
2.注意点
	1)如果ordered是true, 前面的文档出错, 后面的所有文档都不会被写入
	2)如果ordered是false, 前面的文档出错, 后面的所有文档也会被写入
	3)	db.users.insertMany(
            [{_id:1, name:'zs', age:18},{_id:1, name:'ls', age:19},{_id:2, name:'ww', age:20}],
            { ordered: true }
        )
        // 使用insertMany插入多个文档时,主键冲突,会报错
        db.users.find()
        db.users.remove({})  // 移除集合中的所有文档
        db.users.insertMany(
            [{_id:1, name:'zs', age:18},{_id:1, name:'ls', age:19},{_id:2, name:'ww', age:20}],
            { ordered: false }
        )
        db.users.find()
4.6 写入一个或多个文档
1.	db.<collection>.insert(
    <document> or ,[<document>, ...]
        {
            writeConcern: <document>,
            ordered: <boolean>
        }
    );
    insertOne和insertMany结合体
2.注意点
	1)和insertOne/insertMany一样,  集合不存在会自动创建
    2)和insertOne/insertMany一样,  主键冲突会报错
    3)和insertMany一样, 默认都是按顺序插入, 前面的文档出错, 后续所有文档不会被插入

第五章 MongoDB读取文档(查询)

5.1 查询文档
1.	db.<collection>.find(
        <query>,
        <projection>
  	)
  	query: 查询条件, 相当于MySQL中的where
	projection: 投影条件, 规定了结果集中显示那些字段, 相当于MySQL中的 select 字段1, 字段2, .. from 表名;
5.2 查询所有文档
db.<collection>.find();
不传入条件, 默认就是查询所有
5.3 查询满足条件文档
1.db.users.insert([{name:'zs', age:17},{name:'ls', age:18},{name:'ww', age:19}])
    1)单个字段条件
    db.users.find() // 默认会返回指定集合中所有的数据和所以的字段
    db.users.find({name:'zs'}) // 我们可以通过第一个参数指定查询条件, find方法会把所有满足条件的数据返回给我们  类似于mysql中的where
    2)多个字段条件
    db.users.find({name:'zs', age:17}) // 默认是And关系, 也就是默认要求同时满足所有指定的条件, 才会返回对应的数据
    db.users.find({age:17, name:'zs'}) // 注意点: 没有顺序要求, 只要是同时满足多个条件即可
    3)文档中又是文档情况  先删除uers集合
    db.users.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}}]
    )
    4)db.users.find({'book.name':'JavaScript'}) // 如果某一个文档的某一个字段的取值又是一个文档, 那么在判断的时候我们可以通过'字段.文档属性名称'的方式来判断	
5.4 查询指定字段
1. 0表示不显示, 1表示显示
2. 除主键以外, 其它字段不能同时出现0和1(要么不写,写了就必须全是1或者全是0)
    db.users.find({},{_id:0}) 
    // 如果不想查询某一个字段, 那么就可以指定这个字段的投影取值为0
    db.users.find({},{_id:0, name:1, age:1, book:1}) 
    // 如果想查询某一个字段, 那么就可以指定这个字段的投影取值为1
    // 默认情况下如果不指定, 那么所有字段的投影取值都是1
    db.users.find({},{_id:0, name:1, age:1, book:0}) 
    // 除了_id字段以外, 其它的字段不能同时出现0和1
    db.users.find({},{_id:0, book:0}) 
5.5 比较操作符
1.和MySQL一样, MongodDB中也支持很多比较操作符
    1)$eq: 等于 / $ne: 不等于
    2)$gt: 大于 / $gte: 大于等于
    3)$lt: 小于 / $lte: 小于等于
2.使用格式
    db.<collection>.find(
        {<field>: {$<operator>: <value>}},
        <projection>
    )
3.示例
    db.users.insert([{name:'zs', age:17, gender:'男'},{name:'ls', age:18},{name:'ww', age:19}])
    查询名称叫做zs的人
    	db.users.find({name:'zs'});
    	db.users.find({name:{$eq:'zs'}});
    查询所有成年人
    	db.users.find({age:{$gte:18}});
    查询所有未成年人
    	db.users.find({age:{$lt:18}});
    查询所有不是18岁的人
    	db.users.find({age:{$ne:18}});
    注意点: 没有指定字段也算作不等于  没有写也是不等于
    	db.users.find({gender:{$ne: '女'}}) 
    
    注意点: 在做不等于判断的时候, 没有需要判断的字段, 也算作是不等于
5.6 其它比较操作符
1.$in: 匹配和任意指定值相等的文档
  $nin:匹配和任意指定值都不相等的文档
2.使用格式
    db.<collection>.find(
        {<field>: {$<operator>: [<value1>, <value2>, ...]}},
        <projection>
    )
3.实例
    查询名称叫做zs或者ls的人
    	db.users.find({name:{$in:['zs','ls']}});  // 匹配和任意指定值相等的文档
    查询名称不叫zs或者ls的人
    	db.users.find({name:{$nin:['zs','ls']}});  // 匹配和任意指定值都不相等的文档
    查询性别不是男或女的人
    	db.users.find({gender:{$nin:['男', '女']}});
    	
    注意点: 和$ne一样, 如果没有需要判断的字段, 也算作满足条件
    注意点: 没有指定字段也算作不包含
5.7 逻辑操作符
1.	$not: 匹配条件不成立的文档
    	{<field>: {$not: {<expression>}}}
    
    $and: 匹配条件全部成立的文档
    	{<field>: {$and: [{<expression1>}, {<expression2>}, ...}]}
    
    $or : 匹配至少一个条件成立的文档
    	{<field>: {$or: [{<expression1>}, {<expression2>}, ...}]}
    
    $nor: 匹配多个条件全部不成立的文档
    	{<field>: {$nor: [{<expression1>}, {<expression2>}, ...}]}
2.示例:
    1)$not
    //查询所有年龄不等于18岁的人
    	db.users.find({age:{$ne:18}})
   		db.users.find({age:{$not:{$eq:18}}})
    
    //查询不是男人的人
    	db.users.find({gender:{$eq:'男'}})  // 查询性别是男的人
    	db.users.find({gender:{$not:{$eq:'男'}}});  // 查询不是男人的人
    	注意点: $not运算符和$ne/$nin一样, 如果需要查询的字段不存在, 也会算作条件成立
    
    2)$and  就是同时满足多个条件
   	 //查询所有名称叫做zs的未成年人
   	 	// 和下面的写法是一样的
    	db.users.find({$and:[{name:{$eq:'zs'}},{age:{$lt:18}}]})
    
    	// $and后面可以写一个数组,数组中放N个条件
    	db.users.find({$and:[{name:'zs'},{age:{$lt:18}}]})
    	
    	// find第一个参数表示查询条件,里面可以指定多个字段
    	db.users.find({name:'zs', age:{$lt:18}})
    
    3)$or
    //查询所有名称叫做zs或者ls的人
        db.users.find({name:{$in:['zs','ls']}})
        
        // $or后面写一个数组,数组中放多个条件,这些条件是或的关系
        db.users.find({$or:[{name:{$eq:'zs'}},{name:{$eq:'ls'}}]})
        
        db.users.find({$or:[{name:'zs'},{name:'ls'}]})
    
    4)$nor
    //查询所有名称不叫zs或者ls的人
   		db.users.find({name:{$nin:['zs','ls']}})
   		
    	db.users.find({$nor:[{name:'zs'},{name:'ls'}]})
    
    //查询所有名称不叫zs或者性别不是男的人
    	db.users.find({$nor:[{name:'zs'},{gender:'男'}]})
    	
    	注意点: $nor运算符和$ne/$nin/$not一样, 如果需要查询的字段不存在, 也会算作条件成立  
5.8 字段操作符
1.	$exists: 查询包含某个字段的文档
    	{<field>: {$exists: <boolean>}}
    
    $type:   查询指定字段包含指定类型的文档
    	{<field>: {$type: <BSON> or [<BSON1>, <BSON2>]}}
2.查询包含字段gender的人
    db.users.insert([
        {name:'zs', age:17, gender:'男'},
        {name:'ls', age:18},
        {name:'ww', age:19},
        {name:'wc', age:20, gender:'女'}
    ])
    
    // 需求: 要求查询出所有拥有gender属性的文档
    db.users.find({gender:{$exists: true}})
    
3.应用场景:
    // 应用场景: 配合$ne/$nin/$nor/$not来清理数据
    db.users.find({gender:{$ne:'男'}})
    db.users.find({gender:{$ne:'男', $exists:true}})
    
4.查询所有年龄是字符串类型的文档
    db.users.insert([
        {name:'wc', age:'666'},
        {name:'xq', age:'888'},
    ])
    // 需求: 要求查询出所有age属性的取值是字符串类型的文档
    db.users.find({age:{$type:'string'}})
5.9 数组操作符
1.	$all: 匹配数组中包含所有指定查询值的文档
    	{<field>: {$all: [<value1>, <value2>, ...]}}
    
    $elemMatch: 匹配数组中至少有一个能完全匹配所有的查询条件的文档
    	{<field>: {$elemMatch: {<query1>, <query2>, ...}}}

2.示例
    1)查询tags中同时拥有html和js的文档
        db.users.insert([
            {name: 'zs', tags:['html', 'js', 'vue']},
            {name: 'ls', tags:['html', 'react', 'vue']},
            {name: 'ww', tags:['html', 'node', 'js']},
        ])
        
    	db.users.find({tags:{$all:['html', 'js']}})
    2)查询所有名称叫做zs,年龄是18岁的文档
        db.school.insert([
            {
                class: 'one',
                studnets: [
                    {name:'zs', age: 18},
                    {name:'ls', age: 19},
                    {name:'ww', age: 20},
                ]
            },
            {
                class: 'two',
                studnets: [
                    {name:'zs', age: 20},
                    {name:'ls', age: 19},
                    {name:'ww', age: 18},
                ]
            },
        ])
        // 不OK  
        db.school.find({'studnets.name':'z2', 'studnets.age':18})
        // OK
        db.school.find({studnets:{$elemMatch:{name:'zs',age:18}}})
5.10 运算操作符
1.运算操作符
    { <field>: { $regex: /pattern/, $options: '<options>' } }
    { <field>: { $regex: /pattern/<options> } }
    查询满足正则的文档
2.示例
    db.users.insert([
        {name:'zs', age:18},
        {name:'ls', age:19},
        {name:'ww', age:17},
        {name:'Zsf', age:18},
        {name:'xq', age:19},
        {name:'Wz', age:17}
    ])
    // 需求: 要求查询出所有姓z的人(文档)   $regex表示后面写正则  两个//表示正则
    // ^ 以xxx开头     $options表示选项  i表示忽略大小写
    db.person.find({name:{$regex:/^z/, $options: 'i'}})
    
    // 需求: 要求查询出所有姓是z或者x的人(文档)   /^z/i 也是正则  i表示忽略大小写
    db.person.find({name:{$in:[/^z/i, /^x/i]}})
5.11 文档游标(指针)
1.为什么学习前端都要学习MongoDB?
    因为MongoDB原生就支持JavaScript, 也就是我们可以直接在MongoDB中混入JS代码
    MongoDB对前端非常友好
2.什么是文档游标
    我们执行find方法后, find方法其实是有返回值的, find方法会返回一个文档游标(相当于C语言指针)
    find方法就返回一个游标,游标是一个对象。
3.文档游标常用方法
    1)hasNext(): 是否还有下一个文档,如果为true表示还有下一个文档
    2)next():    取出下一个文档
    3)forEach(): 依次取出所有文档,遍历所有的文档
4.文档游标注意点
    1)默认情况下通过文档游标遍历完所有文档后, 系统会在10分钟后自动关闭当前游标
    2)如果不想自动关闭, 我们可以通过noCursorTimeout函数来保持游标一直有效
    3)如果想手动关闭游标, 我们也可以通过close函数来手动关闭游标
5.示例
    // 需求: 往users集合中插入100个文档
    var arr =[];
    for(var i = 0; i < 100; i++){
      arr.push({name:'wc'+i, age:18+i});
    }
    db.users.insertMany(arr)
    // cursor叫游标
    var cursor = db.users.find().noCursorTimeout()
    //cursor[0]
    //cursor[1]
    // cursor.hasNext()表示判断是否还有下一个文档,如果有返回true
    // cursor.next()得到文档
    while(cursor.hasNext()){
       printjson(cursor.next())
    }
    cursor.forEach(printjson)
    cursor.close()
5.12 分页方法
1.	cursor.limit(<number>): 取多少个文档
	cursor.skip(<offset>) : 跳过多少个文档

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

3.分页函数注意点
    // 注意点: MongoDB是支持链式调用的
    // 需求: 跳过前面5个文档, 取出后面的5个文档
    db.users.find().skip(5).limit(5)
    
    // 注意点:在链式调用的时候, 无论skip写在前面还是后面, 都会在limit之前执行
    db.users.find().limit(5).skip(10)
5.13 排序函数
1.排序函数
   1)cursor.sort({field: ordering, ...}): 按照指定规则排序
   2)ordering为1表示升序排序
   3)ordering为-1表示降序排序
2.示例
    准备数据:
    	db.users.insert({name:'wc', age:15})
    	db.users.insert({name:'xq', age:18})
    	db.users.insert({name:'zs', age:9})
    排序:	
    	db.users.find().sort({age:1})
    	db.users.find().sort({age:-1})
3.注意点
    1)find方法默认会取出所有文档
    2)如果有sort,会先执行sort,然后,再分页
    	db.users.find().skip(1).limit(1)
    	db.users.find().skip(1).limit(1).sort({age:-1})
5.14 统计函数
1.统计函数
    cursor.count(<applySkipLimit>): 统计集合中文档的个数
    applySkipLimit默认为false, 表示忽略skip和limit
2.示例
    db.users.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.统计函数注意点
    1)在find函数不提供筛选条件时, count函数会从集合的元数据中取得结果
    2)在单台电脑上是这个结果是准确的,但是如果数据库为分布式结构(多台电脑)时,如果不给find函数提供筛选条件, 那么count函数返回的结果并不一定准确

第六章 MongoDB更新文档

6.1 更新文档
MongoDB中有三个常用的更新方法: save()/update()/findAndmodify()
6.2 save方法
1.save用于往集合里添加一个新文档或者覆盖文档
2.当没有指定文档_id的时候就是新增
3.当指定了集合中已经存在的_id的时候就是覆盖
4.	db.users.insert([
        {name:'zs', age:18},
        {name:'ls', age:19},
        {name:'ww', age:20},
        {name:'zs', age:21},
    ])
    
    db.users.find()
    db.users.save({name:"xq",age:666})  // 表示新增
    
    // ObjectId("3243678fasfdd")表示把字符串转成ObjectId类型
    // 使用新的数据,把老的数据覆盖掉,老的数据没有了
    db.users.save({_id:ObjectId("60e2ad7432ce4fadfb5cce9e"),score:80})
    
6.3 update方法

1)默认情况下,update是使用新的文档,把旧的文档覆盖掉

2)匹配时,默认只会匹配第1个文档

1.	db.collection.update(<filter>, <update>, <options>)
    1)<filter>: 筛选条件
    2)<update>: 新的内容
    3)<options>: 额外配置
    
2.通过update覆盖满足条件数据
	默认情况下如果<update>没有使用更新操作符, 那么就会使用指定的内容覆盖符合条件的内容

3.示例:
    // {name:'zs'}是条件    {name:'zs123'}是新内容
	db.users.update({name:'zs'}, {name:'zs123'})
	
4.注意点:
    // 注意点: update方法默认情况下就是覆盖
    // 如果不想覆盖, 而是想单纯的更新, 那么就必须在第二个参数中使用'更新操作符'
    // 之前ww文档中是没有score,更新时,会把score把name和age覆盖掉
    db.users.update({name:'ww'},{score: 99.9},{})
    
    // 注意点: update方法默认只会更新满足条件的第一个文档
    // 如果想更新所有满足条件的文档, 那么就必须指定第三个参数
    db.users.update({name:'zs'}, {name:'zs', age:55}, {})
    
    // 注意点: 如果在使用update方法的时候, 在第二个参数中指定了_id, 那么就必须保证指定的_id和被更新的文档的_id的取值一致
    // 否则就无法更新, 否则就会报错
    // 开发技巧: 在企业开发中如果需要使用update方法, 那么就不要指定_id
    db.users.update({name:'zs'}, {_id:1, name:'zs', age:55}, {})
    db.users.update({name:'zs'}, {_id:ObjectId("5e9007350718cb6e37ab4515"), name:'zs', age:88}, {})
    
    // 注意点: 如果想更新所有满足条件的文档, 我们可以指定第三个参数的取值multi:true
    // 注意点: 如果指定了multi:true, 那么就必须在第二个参数中使用'更新操作符'
    db.users.update({name:'zs'}, {name:'zs', age:55}, {multi:true})
6.4 更新操作符
默认情况下update会使用新文档覆盖旧文档,如果不想覆盖而是仅仅想更新其中的某些字段,那么我们就需要使用update的更新操作符
6.5 $set更新操作符
1.$set更新操作符
    $set: 更新或者新增字段, 字段存在就是更新, 字段不存在就是新增
    格式: {$set:<value1>, ...}
2.示例:
	// {name:'zs'} 表示更新的条件   $set叫更新操作符  不再是使用新文档覆盖旧文档
	// $set就是用来设置某个文档中的某个字段的值
    db.users.update({name:'zs'}, {$set:{name:'zs123'}})
    db.users.update({name:'ls'}, {$set:{age:'888'}})
    
3.更新内嵌文档和数组
    db.users.insert([
        {name:'zs', age:18},
        {name:'ls', age:19},
        {name:'ww', age:20},
        {name:'zs', age:21},
    ])
    db.users.update({name:'ww'}, {age:55})
    
    // 更新普通字段
    db.users.update({name:'ls'}, {$set:{age:55}})
    
    // {multi:true}批量更新
    db.users.update({name:'zs'}, {$set:{age:88}}, {multi:true})
    
    db.users.insert(
        {
            name:'ww',
            age:18,
            book:{name:'精通mongodb', price:198},
            tags:['html', 'JavaScript']
        }
    )
    // 更新文档字段
    db.users.update({name:'ww'}, {$set:{'book.name': '精通express'}})
    
    // 更新数组字段
    db.users.update({name:'ww'}, {$set: {'tags.0': 'vue'}})
    
4.注意点:
    // 注意点: 如果操作的字段存在, 那么就是更新, 如果操作的字段不存在, 那么就是新增
    db.users.update({name:'ls'}, {$set:{score: 59.5}})
    // 注意点: 如果操作的是数组字段, 如果操作索引不存在, 那么也会自动新增
    // 如果被操作的索引前面没有数据, 那么会自动用null来填充
    db.users.update({name:'ww'}, {$set: {'tags.2': 'react'}})
    db.users.update({name:'ww'}, {$set: {'tags.5': 'node'}})
6.6 $unset更新操作符
1.	$unset更新操作符

    $unset: 删除字段
    格式  :{$unset:{<field>:'', ...}}
2.示例:
    // 删除普通字段
    db.users.update({name:'ls'}, {$unset:{age:''}})
    // 注意点: 如果使用$unset删除某一个字段, 那么后面赋值为任何的内容都不重要,都无所谓
    db.users.update({name:'ls'}, {$unset:{age:'www.baidu.com'}})
    // 删除文档字段中的字段
    db.users.update({name:'ww'}, {$unset:{'book.price': ''}})
    // 删除数组字段中的元素
    // 注意点: 如果删除的是数组字段中的元素, 那么并不会修改数组的长度, 而是用null来填充删除的内容
    db.users.update({name:'ww'}, {$unset:{'tags.1': ''}})
3.注意点:
    1)删除数组元素并不会影响数组的长度, 而是设置为Null
    2)如果删除的字段不存在, 不会做任何操作
6.7 $rename更新操作符
1.$rename更新操作符
    $rename: 重命名字段
    格式  :{$rename:{<field>:<newName>, ...}}
2.示例
    db.users.update({name:'zs'}, {$rename:{name:'MyName'}})
    
    // 注意点: 如果修改的是文档字段中的字段, 那么取值必须写上层级关系
    db.users.update({name:'ww'}, {$rename:{'book.name':'book.BookName'}})
    
    // 注意点: 如果要操作的字段不存在, 那么不会做任何的操作
    db.users.update({name:'ls'}, {$rename:{age:'MyAge'}})
    
    // 注意点: 如果重命名之后的名称已经存在了, 那么已经存在的字段就会被删除
    // 底层的本质: 先调用了$unset删除了原有的book字段, 然后再调用$set修改字段的名称
    db.users.update({name:'ww123'}, {$rename:{name:'book'}})
    
    // 注意点: 不能通过$rename更新操作符来操作数组
    db.users.insert(
        {
            name:'z6',
            age:888,
            book:{name:'mongodb', price:999},
            tags:[{name:'html', price:'123'}, {name:'js', price:456}]
        }
    )
    db.users.update({name:'z6'}, {$rename:{'tags.0.name':'tags.0.TagName'}})
    
4.乾坤大挪移
    // 可以将外层的字段转移到内层的文档中
    db.users.update({name:'z6'}, {$rename:{age:'book.age'}})
    db.users.find()
    
    // 可以将内层文档中的字段, 转移到外层文档中
    db.users.update({name:'z6'}, {$rename:{'book.age':'age'}})
6.8 i n c 和 inc和 incmul更新操作符
1.$inc和$mul更新操作符
    $inc:更新字段值(增加或者减少字段保存的值)
    	格式: {$inc:{<field>: <number>}}
    $mul:更新字段值(乘以或者除以字段保存的值)
    	格式: {$mul:{<field>: <number>}}
2.示例
    db.users.update({name:'zs'}, {$inc:{age:2}})
    db.users.update({name:'zs'}, {$inc:{age:-5}})
    db.users.update({name:'zs'}, {$mul:{age:0.5}})
    db.users.update({name:'zs'}, {$mul:{age:2}})
3.注意点:
    1)只能操作数字类型字段
    2)如果操作的字段不存在, 会自动新增这个字段
       不同的是$inc会把操作的值赋值给新增的字段, 而$mul会自动赋值为0
       
    	db.users.update({name:'zs'}, {$inc:{weight:2}})
    	db.users.update({name:'zs'}, {$mul:{height:2}})
6.9 m i n 和 min和 minmax更新操作符
1.$min和$max更新操作符
    $min:比较保留更小字段值
    格式: {$min:{<field>: <value>}}
    $max:比较保留更大字段值
    格式: {$max:{<field>: <value>}}

2.示例
    db.users.insert({name:'z3', age:33})
    db.users.update({name:'z3'}, {$min:{age:50}})
    db.users.update({name:'z3'}, {$min:{age:18}})
    db.users.update({name:'z3'}, {$max:{age:5}})
    db.users.update({name:'z3'}, {$max:{age:55}})
3.注意点:
    // 注意点: 如果操作的字段不存在, 那么会自动增加, 并且会将操作的值赋值给新增的字段
    db.users.update({name:'z3'}, {$min:{weight:120}})
    db.users.update({name:'z3'}, {$max:{height:175}})
    
    // 注意点: 和$inc/$mul不同, $min/$max不仅仅能操作数值类型的字段, 只要是可以比较的字段都可以操作
    db.users.insert({name:'def', age:666})  
    db.users.update({name:'def'}, {$min:{name:'efg'}})
    db.users.update({name:'def'}, {$min:{name:'cde'}})
    
    // 注意点: 不是相同的数据类型也可以进行比较
    db.users.update({name:'z3'}, {$min:{age:null}})
6.10 $addToSet数组更新操作符
1.$addToSet数组更新操作符
    1)$addToSet: 向数组字段中添加元素
    2)格式: {$addToSet: {<field>:<values>, ...}}
2.示例
    db.users.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.users.update({name:'zs'}, {$addToSet:{tags:'react'}})
3.注意点
    // 注意点:如果操作的元素不存在, 那么会自动新增, 并且将操作的值赋值给新增的数组字段
    db.users.update({name:'zs'}, {$addToSet:{other:'123'}})
    
    // Set 里面不能包含重复元素
    // 注意点: $addToSet会自动去重, 如果添加的元素已经存在了, 那么就不会添加了
    db.users.update({name:'zs'}, {$addToSet:{other:'123'}})
    
    // 注意点: 如果往数组字段中添加的是文档类型, 那么必须一模一样才会去重
    // 可以添加
    db.users.update({name:'zs'}, {$addToSet:{books:{name:'css', price:88}}}) 
    // 不能添加  原因是人家本身内部就有一个一模一样的文档
    db.users.update({name:'zs'}, {$addToSet:{books:{name:'html', price:66}}})
    // 可以添加  因为文档中的顺序不一样
    db.users.update({name:'zs'}, {$addToSet:{books:{price:66, name:'html'}}})
    
    // 注意点: 如果往数组字段中添加的是数组, 那么也必须一模一样才会去重
    // 向数组中添加一个新的数组
    db.users.update({name:'ls'}, {$addToSet:{tags:['1', '2']}})
    // 添加失败,因为set中不能添加重复元素
    db.users.update({name:'ls'}, {$addToSet:{tags:['1', '2']}})
    // 可以添加成功
    db.users.update({name:'ls'}, {$addToSet:{tags:['2', '1']}})
    
    // 注意点: 如果往往数组字段中添加的是数组, 那么默认情况下会将整个数组作为一个元素添加进去
    //      如果不想诶一个整体添加进去,那么必须使用$each来添加
    db.users.update({name:'ls'}, {$addToSet:{tags:{$each: ['1', '2', '3']}}})
6.11 $push数组更新操作符
1.$push: 向数组字段中添加元素(不去重)
2.格式: {$push: {<field>:<value1>, ...}}

// 可以添加重复元素
db.users.update({name:'z3'},{$push:{tags:'react'}})
db.users.update({name:'ls'}, {$push:{tags:"3"}})
6.12 $pop数组更新操作符
1.$pop数组更新操作符
    $pop: 从数组字段中删除元素   删除第1个或者最的1个
    格式: {$pop: {<field>:<1|-1>, ...}}
2.示例
    db.users.update({name:'zs'}, {$pop:{tags:1}}) #删除最后一个
    db.users.update({name:'zs'}, {$pop:{tags:-1}})#删除第一个
3.注意点
	数组中的元素都被删除以后, 仍然会保留空的数组
6.13 $pull数组更新操作符
1.$pull数组更新操作符
    $pull: 从数组字段中删除特定元素
    格式: {$pull: {<field>:<value|condition>, ...}}
2.示例
    db.users.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.users.update({name:'zs'}, {$pull:{tags:'js'}})
    // /^a/  // 表示正则  ^a表示以a开头  
    db.users.update({name:'ls'}, {$pull:{tags:/^a/}})
3.注意点
    // 注意点: 如果要删除的元素是一个数组, 那么必须一模一样才能删除
    db.users.update({name:'zs'}, {$pull:{tags:['2', '1']}})
    db.users.update({name:'zs'}, {$pull:{tags:['1', '2']}})
    
    // 注意点: 如果要删除的元素是一个文档, 那么不用一模一样也可以删除
    db.users.update({name:'zs'}, {$pull:{books:{price:66, name:'html'}}})
    db.users.update({name:'zs'}, {$pull:{books:{name:'js'}}})
6.14 $pullAll数组更新操作符
1.$pullAll数组更新操作符
    $pullAll: 从数组字段中批量删除特定元素
    格式: {$pullAll: {<field>: [<value1>, <value2>, ...], ...}}
2.示例
    db.users.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.users.update({name:'zs'}, {$pullAll:{tags:['html', 'js']}})
3.注意点
    // 注意点: 和$pull一样, 如果删除的是数字字段中的数组元素, 那么必须一模一样才能删除
    db.users.update({name:'zs'}, {$pullAll:{tags:[['2','1']]}})
    db.users.update({name:'zs'}, {$pullAll:{tags:[['1','2']]}})
    
    // 注意点: 和$pull不一样, 如果删除的是数组字段中的文档元素, 那么也必须一模一样才能删除
    db.users.update({name:'zs'}, {$pullAll:{books:[{price:66,name:'html'}]}})
    db.users.update({name:'zs'}, {$pullAll:{books:[{name:'html',price:66}]}})
6.15 和 和 []数组更新操作符
1.	$和$[]数组更新操作符
	$: 更新数组中满足条件的特定元素
    格式:
        db.<collection>.update(
            { <array field>:<query selector> }
            { <update operator>: {'<array field>.$':value}}
        )
    $[]: 更新数组中所有元素
        db.<collection>.update(
             { <update operator>: {'<array field>.$[]':value}}
        )
2.示例
    db.users.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.users.find()
    db.users.update(
        {name:'zs', tags:'js'},
        {$set:{'tags.$':'JavaScript'}}
    )
    db.users.update(
        {name:'zs'},
        {$set:{'tags.$[]': 'wc'}}
    )

第七章 MongoDB删除文档

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

准备数据:

db.users.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},
])

删除操作:

//删除所有满足条件
// 注意点: 和update方法不同, remove方法默认就会删除所有满足条件的数据
db.users.remove({name:'zs'})

//删除第一个满足条件
db.users.remove({name:'ls'},{justOne:true})

//删除所有文档
db.users.remove({})

第八章 MongoDB聚合

8.1 什么是聚合操作?
1.聚合操作就是通过一个方法完成一系列的操作
2.在聚合操作中, 每一个操作我们称之为一个阶段,
  聚合操作会将上一个阶段处理结果传给下一个阶段继续处理,
  所有阶段都处理完毕会返回一个新的结果集给我们
8.2 聚合操作格式
db.<collection>.aggregate(<pipeline>, <options>)
<pipeline>: 定义每个阶段操作
<options> : 聚合操作额外配置
8.3 聚管道阶段 $project
1.	$project: 对输入文档进行再次投影
    作用: 按照我们需要的格式生成结果集
    格式: {$project:{<field>:<value>}}
2.示例
    db.users.insert([
        {name:{firstName:'w', lastName:'c'}, age:18, book:{name:'玩转HTML', price: 88}},
        {name:{firstName:'x', lastName:'q'}, age:17, book:{name:'玩转JavaScript', price: 99}}
    ])
    db.users.aggregate([
        {
            $project:{
                _id:0,
                clientName: '$name.firstName',
                clientAge: '$age'
            }
        }
    ])
3.聚合表达式
    1)字段路径表达式
        $<filed>: 使用$来指示字段路径
        $<filed>.<sub-field>: 使用$和.来指示内嵌文档字段路径
    2)字段路径表达式示例
        $name
        $book.name
4.注意点:
    // 注意点: $project修改的是结果集而不是原有的集合
    db.users.find()
    // 注意点: 如果字段表达式使用了不存在的字段, 那么会自动用Null填充
    db.users.aggregate([
        {
            $project:{
                _id:0,
                fullName: ['$name.firstName', '$name.middleName','$name.lastName'],
                clientAge: '$age'
            }
        }
    ])
8.4 聚合管道阶段 $match
1.聚合管道阶段
    $match: 和find方法中的第一个参数一样, 用于筛选符合条件的文档
    格式  : {$match:{<query>}}
2.示例
    db.users.aggregate([
        {
            $match:{  // 引用之前结果集中的数据时,如果是值,使用使用$name  如果是键,不需要使用$
                'name.firstName':'w'
            }
        }
    ])
    db.users.aggregate([
        {
            $match:{
                'name.firstName':'w'
            }
        },
        {
            $project:{
                _id:0,
                clientName: '$name.firstName',
                clientAge: '$age'
            }
        }
    ])
3.使用技巧:
    应该在聚合操作的最前面使用$match, 这样可以有效减少处理文档的数量, 大大提升处理的效率
8.5 聚合管道阶段 $limit
1.聚合管道阶段
    1)$limit: 和游标的limit方法一样, 用于指定获取几个文档
    格式  : {$limit:<number>}
    2)$skip : 和游标的skip方法一样, 用于指定跳过几个文档
    格式  : {$skip:<number>}
2.示例
    db.users.aggregate([
        {
            $skip:1
        },
        {
            $limit:1
        },
        {
            $project:{
                _id:0,
                clientName: '$name.firstName',
                clientAge: '$age'
            }
        }
    ])
8.6 聚合管道阶段 $unwind
1.聚合管道阶段
    $unwind: 展开数组字段
    格式   : {$unwind:{path:<field>}}
2.示例:
    db.users.update({'name.firstName':'w'}, {$set:{tags:['html', 'js']}})
    db.users.update({'name.firstName':'x'}, {$set:{tags:'vue'}})
    db.users.aggregate([
        {
            $unwind:{
                path:'$tags'
            }
        }
    ])
3.注意点:
	1)$unwind会为数组中的每个元素创建一个新的文档
	2)可以通过includeArrayIndex属性添加展开之后的元素在原数组中的位置
    db.users.aggregate([
        {
            $unwind:{
                path:'$tags',
                includeArrayIndex: 'index'
            }
        }
    ])
	3)如果需要展开的字段不存在, 或者数组中没有元素, 或者为null, 会被unwind剔除
    db.users.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}
    ])
	4)如果想让unwind不剔除不存在/没有元素/为Null的文档, 那么可以添加preserveNullAndEmptyArrays属性
    db.person.aggregate([
        {
            $unwind:{
                path:'$tags',
                includeArrayIndex: 'index',
                preserveNullAndEmptyArrays: true
            }
        }
    ])
8.7 聚合管道阶段 $sort
1.聚合管道阶段
    $sort: 和文档游标sort方法一样, 对文档进行排序
    格式   : {$sort:{<field>>:1|-1}}
2.示例
    db.person.aggregate([
        {
            $sort:{
                age: 1
            }
        }
    ])
8.8 聚合管道阶段 $lookup
1.聚合管道阶段
    1)$lookup: 用来做关联查询
    2)格式:
    {$lookup:{
        from: 关联集合名称,
        localField: 当前集合中的字段名称,
        foreignField:关联集合中的字段名称,
        as: 输出字段的名称
    }}
2.示例:
    db.users.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.users.aggregate([
        {
            $lookup:{
                from: 'books',
                localField: 'books',
                foreignField: 'name',
                as: 'booksData'
            }
        };
    ])
3.和unwind阶段结合使用
    1)可以有效的过滤掉无效数据
    2)可以给每个匹配的结果生成一个新的文档
    db.users.aggregate([
        {
            $unwind:{
                path:'$books'
            }
        },
        {
            $lookup:{
                from: 'books',
                localField: 'books',
                foreignField: 'name',
                as: 'booksData'
            }
        }
    ])
8.9 聚合管道阶段 $lookup
1.聚合管道阶段
    $lookup: 用来做关联查询
    格式   :
    {$lookup:{
        from: 关联集合名称,
        let: {定义给关联集合的聚合操作使用的当前集合的常量},
        pipeline: [关联集合的聚合操作]
        as: 输出字段的名称
    }}
2.示例:
    不相关查询
    db.users.aggregate([
        {
            $lookup:{
                from: 'books',
                pipeline: [
                    {
                        $match:{
                            price:{$gte:100}
                        }
                    }
                ],
                as: 'booksData'
            }
        }
    ])
    相关查询
    db.users.aggregate([
        {
            $lookup:{
                from: 'books',
                let: { bks: '$books'},
                pipeline: [
                    {
                        $match:{
                            $expr:{
                                $and:[
                                {$gte: ['$price', 100]},
                                {$in: ['$name', '$$bks']}
                                ]
                            }
                            //price:{$gte:100}
                        }
                    }
                ],
                as: 'booksData'
            }
        }
    ])
3.系统变量表达式
	$$<variable>: 使用$$来指示系统变量
8.10 聚合管道阶段 $group
1.聚合管道阶段
    1)$group: 对文档进行分组
    2)格式:
    {$group:{
        _id:<expression>,
        <field1>: {<accumulator1>: <expression1>}
        ... ...
    }}
    3)_id: 定义分组规则
    4)<field>: 定义新字段
2.示例
    db.users.insert([
        {name:'zs', age:10, city:'北京'},
        {name:'ls', age:20, city:'上海'},
        {name:'ww', age:30, city:'北京'},
        {name:'zl', age:40, city:'上海'},
        {name:'xq', age:50, city:'北京'},
        {name:'jjj', age:60, city:'广州'},
    ])
    db.users.aggregate([
        {$group:{
            _id:'$city',
            totalAge:{$sum:'$age'},
            avgAge:{$avg:'$age'},
            minAge:{$min:'$age'},
            maxAge:{$max:'$age'},
            totalName:{$push:'$name'}
        }}
    ])
8.11 聚合管道阶段 $out
1.聚合管道阶段
    1)$out: 前面阶段处理完的文档写入一个新的集合
    2)格式: {$out: <new collection name>}
2.示例:
    db.users.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写入一个已经存在的集合, 那么集合中的原有数据会被覆盖
8.12 聚合操作额外配置
1.聚合操作额外配置
    1)db.<collection>.aggregate(<pipeline>, <options>)
    2)格式: {allowDiskUse: <boolean>}
    	allowDiskUse默认取值是false, 默认情况下管道阶段占用的内存不能超过100M,如果超出100M就会报错
    3)如果需要处理的数据比较多, 聚合操作使用的内存可能超过100M, 那么我们可以将allowDiskUse设置为true
    4)如果allowDiskUse设置为true, 那么一旦超出100M就会将操作的数据写入到临时文件中, 然后再继续操作
8.13 字段路径表达式
1.字段路径表达式
    1)$<filed>: 使用$来指示字段路径
    2)$<filed>.<sub-field>: 使用$和.来指示内嵌文档字段路径
2.示例
    1)$name
    2)$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:'Wang'}, 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'} // 常量表达式
        }}
    ])
8.14 数据类型转换操作符
1.数据类型转换操作符
    1)MongoDB对于文档的格式并没有强制性的要求, 同一个集合中存储的文档, 字段的个数和数据类型都可以不同
    对与文档的格式没有强制性的要求是MongoDB的一大优势, 但是同时也增加了数据消费端的使用难度
    因为我们在使用数据的时候, 有可能同一个字段取出来的数据类型是不同的, 这样非常不利于我们后续操作
    所以也正是因为如此, MongoDB在4.0中推出了$convert数据类型转换操作符
    2)通过$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:'xq', timestamp:'Sunday'},
    {name:'xx'},
    ])
    db.person.aggregate([
        {$project:{
            _id:0,
            timestamp:{
                $convert:{
                    input:'$timestamp',
                    to:'date',
                    onError: '不支持的转换类型',
                    onNull: '没有需要转换的数据'
                }
            }
        }}
    ])

第九章 数据模型

9.1 文档之间关系
1.MongoDB对于文档的格式并没有强制性的要求, 但不等于我们不能在文档中表达数据的关系
  在MongoDB中我们可以通过'内嵌式结构'和'规范式结构'来表达文档之间的关系

2.内嵌式结构
在一个文档中又包含了另一个文档, 我们就称之为内嵌式结构
例如:
    {
        name:'zs',
        age:'18',
        card:{
            num:'420626200002023556',
            date: 88
        }
    }
3.规范式结构
将文档存储在不同的集合中, 然后通过某一个字段来建立文档之间的关系, 我们就称之为规范式
    {
        _id: 1,
        num:'420626200002023556',
        date: 88
    }
    {
        name:'zs',
        age:'18',
        cardId: 1
    }
9.2 文档一对一关系
一个人有一张身份证
1.内嵌式结构
    db.person.insert({
        name:'zs',
        age:'18',
        card:{
            num:'420626200002023556',
            date: 88
        }
    })
    db.person.find({name:'zs'})
    优势: 一次查询就能得到所有数据
    劣势: 如果数据比较复杂, 不方便管理和更新
    应用场景: 数据不复杂/查询频率较高数据
2.规范式结构
    db.card.insert({
        _id: 123,
        num:'420626200002023556',
        date: '2022-12-08',
        userId: 456
    })
    db.person.insert({
        _id: 456,
        name:'zs',
        age:'18',
        cardId: 123
    })
    db.person.aggregate([
        {$lookup:{
            from: 'card',
            localField: 'cardId',
            foreignField: '_id',
            as: 'card'
        }}
    ])
    优势: 如果数据比较复杂, 也方便管理和更新
    劣势: 查询数据相对内嵌结果稍微有点复杂
    应用场景: 数据比较复杂/更新频率较高数据
9.3 文档一对多关系
一个人有多本书
1.内嵌式结构
    db.person.insert({
        name:'zs',
        age:'18',
        books:[{
            name:'玩转HTML',
            price: 88
        },
        {
            name:'玩转CSS',
            price: 88
        }]
    })
    db.person.find({name:'zs'})
    优势: 一次查询就能得到所有数据
    劣势: 冗余数据较多, 不方便管理和更新
    应用场景: 数据不复杂/查询频率较高数据
2.规范式结构
    db.books.insert([{
        _id: 1,
        name:'玩转HTML',
        price: 88,
        userId:123
    },
    {
        _id: 2,
        name:'玩转CSS',
        price: 88,
        userId:123
    }])
    db.person.insert({
        _id: 123,
        name:'ls',
        age:'20',
        booksId:[1, 2]
    })
    db.person.aggregate([
        {$lookup:{
            from: 'books',
            localField: 'booksId',
            foreignField: '_id',
            as: 'books'
        }}
    ])
    优势: 冗余数据较少, 更新较为方便
    劣势: 查询数据相对内嵌结果稍微有点复杂
    应用场景: 数据比较复杂/更新频率较高数据
9.4 文档多对多关系
一个学生有多个老师
一个老师有多个学生
1.内嵌式结构
    db.students.insert([{name:'zs', teachers:[{name:'taobao'}, {name:'baidu'}]},
    {name:'ls', teachers:[{name:'taobao'}, {name:'baidu'}]}])

    db.teachers.insert([{name:'taobao', students:[{name:'zs'}, {name:'ls'}]},
    {name:'baidu', students:[{name:'zs'}, {name:'ls'}]}])
    db.students.find({name:'zs'})
    db.teachers.find({name:'baidu'})
    优势: 一次查询就能得到所有数据
    劣势: 冗余数据较多, 更新和管理较为复杂
    应用场景: 数据比较简单/查询频率较高数据
2.规范式结构
    db.students.insert([{_id:1, name:'zs'},{_id:2, name:'ls'}])
    db.teachers.insert([{_id:3, name:'taobao'},{_id:4, name:'baidu'}])
    db.relation.insert([{stuId:1, teacherId:3},{stuId:1, teacherId:4},{stuId:2, teacherId:3},{stuId:2, teacherId:4}])
    db.students.aggregate([
        {$lookup:{
            from: 'relation',
            localField: '_id',
            foreignField:'stuId',
            as: 'relation'
        }},
        {$lookup:{
            from: 'teachers',
            localField: 'relation.teacherId',
            foreignField:'_id',
            as: 'teachers'
        }},
        {$project:{_id:0, name:1, teachers:1}}
    ])
    优势: 冗余数据较少, 更新较为方便
    劣势: 查询数据相对内嵌结果稍微有点复杂
    应用场景: 数据比较复杂/更新频率较高数据

第十章 mongoose

对于原生node操作mongodb的使用,直接百度,用的时候,直接查,不需要记:

但是一般情况下,我们写项目时,会直接使用mongoose

10.1 mongoose使用
# 什么是Mongoose?
- 像操作JS对象一个,去操作集合
http://www.mongoosejs.net/
https://mongoosejs.com/

# Mongoose和MongoDB映射关系
在Mongoose中JS中的一个模型就对应数据库中的一个集合
在Mongoose中JS中的一个对象就对应集合中的一个文档
在Mongoose中JS中的一个对象的属性就对应文档的一个字段

# 使用步骤
// 1.定义集合规则
cosnt 规则名称 = new Schema({
    name: String,
    age: Number
});

// 2.利用规则创建一个集合
let User = mongoose.model('User', userSchema);

// 3.创建一个文档
let u = new User({
    name:'zs',
    age:18
});

// 4.操作集合和文档
只要是通过Mongoose定义的模型, 那么Mongoose就会自动给这个模型添加很多操作集合和文档的方法
以后我们就可以直接通过模型操作集合, 通过模型创建出来的对象操作数据

代码如下:

// 1.导入mongoose
const mongoose = require('mongoose');

/*
mongodb://MongoDB服务器IP地址:MongoDB服务器端口号/需要打开的数据库名称
* */
// 2.利用mongoose链接MongoDB服务器
mongoose.connect('mongodb://127.0.0.1:27017/demo');

// 3.监听链接成功还是失败
let db = mongoose.connection;
db.on('error', (err)=>{
    console.log(err, '连接失败');
});
db.once('open', function() {
    console.log('连接成功');
});
db.once('close', function() {
    console.log('断开连接');
});

// 1.定义集合中存储数据规则
let userSchema = new mongoose.Schema({
    name: String,
    age: Number
});
// 2.利用规则创建集合
// 注意点: 只要创建好了模型, 那么以后就可以使用模型来操作这个集合
// 注意点: mongoose会自动将我们指定的集合名称变成复数
let User = mongoose.model('User', userSchema);

// 3.利用集合创建文档
// 注意点: 只要创建好了对象, 那么以后就可以使用对象来操作文档
let u = new User({ name: 'zs', age:18 });

// 4.操作文档
u.save((err, product) => {
    if (!err){
        console.log('文档保存成功');
        console.log(product);
    }
});
10.2 CRUD
// 1.导入mongoose
const mongoose = require('mongoose');

// 2.利用mongoose链接MongoDB服务器
mongoose.connect('mongodb://127.0.0.1:27017/blog');

// 3.监听链接成功还是失败
let db = mongoose.connection;
db.on('error', (err)=>{
    console.log(err, '连接失败');
});
db.once('open', function() {
    console.log('连接成功');
});
db.once('close', function() {
    console.log('断开连接');
});

// 1.定义集合中存储数据规则
let userSchema = new mongoose.Schema({
    name: String,
    age: Number
});

// 2.利用规则创建集合
let User = mongoose.model('User', userSchema);

// 增加
/*
User.create({name:'zs', age:666}, (err, result)=>{
    if(!err){
        console.log('插入成功');
        console.log(result);
    }
});
*/
/*
User.create([
        {name:'ls', age:18},
        {name:'ls', age:22},
        {name:'ww', age:21},
        {name:'zl', age:23},
        {name:'xq', age:33},
    ],
    (err, result)=>{
    if(!err){
        console.log('插入成功');
        console.log(result);
    }
});
 */
/*
(async ()=>{
    let result = await User.create([
            {name:'ls', age:18},
            {name:'ls', age:22},
            {name:'ww', age:21},
            {name:'zl', age:23},
            {name:'xq', age:33},
        ]);
    console.log(result);
})();
 */

// 查询
/*
User.find({},{},(err, docs)=>{
    if(!err){
        console.log(docs);
    }
});
 */
/*
User.find({},{_id:0, name:1, age:1},(err, docs)=>{
    if(!err){
        console.log(docs);
    }
});
 */
/*
User.find({name:'ls'},{_id:0, name:1, age:1},(err, docs)=>{
    if(!err){
        console.log(docs);
    }
});
 */
/*
User.find({},{_id:0, name:1, age:1},{ skip: 5, limit: 5},(err, docs)=>{
    if(!err){
        console.log(docs);
    }
});
 */
/*
(async ()=>{
    let result = await User.find({},{_id:0, name:1, age:1},{ skip: 5, limit: 5});
    console.log(result);
})();
*/

// 修改
/*
User.update({name:'ls'},{$set:{age:888}},(err, docs)=>{
    if(!err){
        console.log('更新成功');
        console.log(docs);
    }
});
 */
/*
User.update({name:'ls'},{$set:{age:888}}, {multi: true},(err, docs)=>{
    if(!err){
        console.log('更新成功');
        console.log(docs);
    }
});
 */
/*
(async ()=>{
   let result = await User.update({name:'ls'},{$set:{age:123}}, {multi: true});
   console.log(result);
})();
 */

// 删除
/*
User.remove({name:'ww'}, {}, (err, docs)=>{
    if(!err){
        console.log('删除成功');
        console.log(docs);
    }
});
 */
/*
User.deleteOne({name:'xq'}, (err, docs)=>{
    if(!err){
        console.log('删除成功');
        console.log(docs);
    }
});
 */
(async ()=>{
    let result = await User.deleteOne({name:'xq'});
    console.log(result);
})();

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340