MongoDB

1. MongoDB简介

 MongoDB是一个基于分布式文件存储的NoSQL数据库,具有以下特点:

  • 易用性 

  MongoDB是一个面向文档(document-oriented)的数据库,而不是关系型数据库。不采用关系型主要是为了获得更好的扩展性。当然还有一些其他好处,与关系数据库相比,面向文档的数据库不再有“行“(row)的概念,取而代之的是更为灵活的“文档”(document)模型。通过在文档中嵌入文档和数组,以面向文档的方法,实现用一条记录来表现复杂的层级关系,这与现代的面向对象语言的开发者对数据的看法一致。另外,不再有预定义模式(predefined schema):文档的键(key)和值(value)不再是固定的类型和大小。由于没有固定的模式,根据需要添加或删除字段变得更容易了。通常由于开发者能够进行快速迭代,所以开发进程得以加快。而且,实验更容易进行。开发者能尝试大量的数据模型,从中选一个最好的。

  • 易扩展性 

  应用程序数据集的大小正在以不可思议的速度增长。随着可用带宽的增长和存储器价格的下降,即使是一个小规模的应用程序,需要存储的数据量也可能大的惊人,甚至超出了很多数据库的处理能力。过去非常罕见的T级数据,现在已经是司空见惯了。由于需要存储的数据量不断增长,开发者面临一个问题:应该如何扩展数据库,分为纵向扩展和横向扩展,纵向扩展是最省力的做法,但缺点是大型机一般都非常贵,而且当数据量达到机器的物理极限时,花再多的钱也买不到更强的机器了,此时选择横向扩展更为合适,但横向扩展带来的另外一个问题就是需要管理的机器太多。MongoDB的设计采用横向扩展。面向文档的数据模型使它能很容易地在多台服务器之间进行数据分割。MongoDB能够自动处理跨集群的数据和负载,自动重新分配文档,以及将用户的请求路由到正确的机器上。这样,开发者能够集中精力编写应用程序,而不需要考虑如何扩展的问题。如果一个集群需要更大的容量,只需要向集群添加新服务器,MongoDB就会自动将现有的数据向新服务器传送

  • 丰富的功能

  MongoDB作为一款通用型数据库,除了能够创建、读取、更新和删除数据之外,还提供了一系列不断扩展的独特功能 #1、索引 支持通用二级索引,允许多种快速查询,且提供唯一索引、复合索引、地理空间索引、全文索引 #2、聚合 支持聚合管道,用户能通过简单的片段创建复杂的集合,并通过数据库自动优化 #3、特殊的集合类型 支持存在时间有限的集合,适用于那些将在某个时刻过期的数据,如会话session。类似地,MongoDB也支持固定大小的集合,用于保存近期数据,如日志 #4、文件存储 支持一种非常易用的协议,用于存储大文件和文件元数据。MongoDB并不具备一些在关系型数据库中很普遍的功能,如链接join和复杂的多行事务。省略这些的功能是处于架构上的考虑,或者说为了得到更好的扩展性,因为在分布式系统中这两个功能难以高效地实现

  • 卓越的性能

     MongoDB的一个主要目标是提供卓越的性能,这很大程度上决定了MongoDB的设计。MongoDB把尽可能多的内存用作缓存cache,视图为每次查询自动选择正确的索引。总之各方面的设计都旨在保持它的高性能。虽然MongoDB非常强大并试图保留关系型数据库的很多特性,但它并不追求具备关系型数据库的所有功能。只要有可能,数据库服务器就会将处理逻辑交给客户端。这种精简方式的设计是MongoDB能够实现如此高性能的原因之一。

2. MongoDB基础知识


SQL术语/概念MongoDB术语/概念解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins   表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键
  • 1)文档

  文档是MongoDB的核心概念。文档就是键值对的一个有序集{'msg':'hello','foo':3}。类似于python中的有序字典。

文档键命名规范:
#1、文档中的键/值对是有序的。
#2、文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
#3、MongoDB区分类型和大小写。
#4、MongoDB的文档不能有重复的键。
#5、文档中的值可以是多种不同的数据类型,也可以是一个完整的内嵌文档。文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
#6、键不能含有 (空字符)。这个字符用来表示键的结尾。
#7、.和$有特别的意义,只有在特定环境下才能使用。
#8、以下划线"_"开头的键是保留的(不是严格要求的)。
  • 2)集合

  集合就是一组文档。如果将MongoDB中的一个文档比喻为关系型数据的一行,那么一个集合就是相当于一张表

#1、集合存在于数据库中,通常情况下为了方便管理,不同格式和类型的数据应该插入到不同的集合,但其实集合没有固定的结构,这意味着我们完全可以把不同格式和类型的数据统统插入一个集合中。

#2、组织子集合的方式就是使用“.”,分隔不同命名空间的子集合。
比如一个具有博客功能的应用可能包含两个集合,分别是blog.posts和blog.authors,这是为了使组织结构更清晰,这里的blog集合(这个集合甚至不需要存在)跟它的两个子集合没有任何关系。在MongoDB中,使用子集合来组织数据非常高效,值得推荐。

#3、当第一个文档插入时,集合就会被创建。合法的集合名:
集合名不能是空字符串""。
集合名不能含有字符(空字符),这个字符表示集合名的结尾。
集合名不能以"system."开头,这是为系统集合保留的前缀。
用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。
  •  3)数据库

  数据库:在MongoDB中,多个文档组成集合,多个集合可以组成数据库

数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串:
#1、不能是空字符串("")。
#2、不得含有' '(空格)、.、$、/、和 (空字符)。
#3、应全部小写。
#4、最多64字节。

有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
#1、admin: 从身份认证的角度讲,这是“root”数据库,如果将一个用户添加到admin数据库,这个用户将自动获得所有数据库的权限。再者,一些特定的服务器端命令也只能从admin数据库运行,如列出所有数据库或关闭服务器
#2、local: 这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中
#3、config: MongoDB用于分片设置时,分片信息会存储在config数据库中

 注意:把数据库名添加到集合名前,得到集合的完全限定名,即命名空间

例如:
如果要使用cmd数据库中的blog.posts集合,这个集合的命名空间就是 cmd.blog.posts。命名空间的长度不得超过121个字节,且在实际使用中应该小于100个字节

3. MongoDB基本使用

3.1 安装与制作服务器

#step1. 在官网下载MongoDB,官网下载地址:
https://www.mongodb.com/download-center/community?ct=atlasheader
#step2. 安装选择custom模式,自定义目录,比如E:softMongoDB,一路next直到结束
#step3. 安装路径为:E:softMongoDB,将E:softMongoBDin目录加入环境变量
#step4. 新建目录与文件
E:softMongoDBdatadb
E:softMongoDBlogmongodb.log
#step5. 新建配置文件E:softMongoDBectmongod.cfg
systemLog:
   destination: file
   path: "E:\soft\MongoDB\log\mongodb.log"
   logAppend: true
storage:
   journal:
      enabled: true
   dbPath: "E:\soft\MongoDB\data\db"
net:
   bindIp: 0.0.0.0
   port: 27017
setParameter:
   enableLocalhostAuthBypass: false
#step6. 启用配置
#用DOS进入d:mongodbin文件夹
#使用命令mongod --config E:softmongodbetcmongodb.conf
#不要关闭窗口,打开浏览器输入:http://localhost:27017访问MongoDB服务器,返回"It looks like you are trying to access MongoDB over HTTP on the native driver port"即安装成功.
#step7. 制作为windows服务
法1:以管理员身份运行:mongod --config "E:softMongoDBetcmongod.cfg" --bind_ip 0.0.0.0 --install
法2:直接在命令行指定配置
mongod --bind_ip 0.0.0.0 --port 27017 --logpath E:softMongoDBlogmongod.log --logappend --dbpath E:softMongoDBdatadb  --serviceName "MongoDB" --serviceDisplayName "MongoDB"  --install
#step8. 启动关闭服务,移除服务
net start MongoDB
net stop MongoDB
mongod --remove#step9. 登录mongoDB
成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作。

mongoDB中文书籍推荐:MongoDB权威指南

3.2 账号管理

#1、创建账号
use admin
db.createUser(
  {
    user: "root",
    pwd: "123",
    roles: [ { role: "root", db: "admin" } ]
  }
)
#说明:上面加粗的就是执行的命令,user:用户名,pwd:密码,roles:指定用户的角色,可以用一个空数组给新用户设定空角色;在roles字段,可以指定内置角色和用户定义的角色。role里的角色可以选:
Built-In Roles(内置角色):
    1. 数据库用户角色:read、readWrite;
    2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;
    3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
    4. 备份恢复角色:backup、restore;
    5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
    6. 超级用户角色:root  
    // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
    7. 内部角色:__system
具体角色说明:
  Read:允许用户读取指定数据库
  readWrite:允许用户读写指定数据库
  dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
  userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
  clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
  readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
  readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
  userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
  dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
  root:只在admin数据库中可用。超级账号,超级权限
#现在创建一个帐号,该账号有grant权限,即:账号管理的授权权限。注意一点,帐号是跟着库走的,所以在指定库里授权,必须也在指定库里验证(auth)。
use admin
switched to db admin
> db.createUser(
...   {
...     user: "dba",
...     pwd: "dba",
...     roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
...   }
... )
Successfully added user: {
    "user" : "dba",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}
#刚建立了 userAdminAnyDatabase 角色,用来管理用户,可以通过这个角色来创建、删除用户。验证:需要开启auth参数。
C:WINDOWSsystem32>mongo
MongoDB shell version v3.4.10
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.10
> show dbs; #没有验证,导致没权限。

2019-04-01T14:38:28.951+0800 E QUERY [thread1] Error: listDatabases failed:{
"ok" : 0,
"errmsg" : "not authorized on admin to execute command { listDatabases: 1.0 }",
"code" : 13,
"codeName" : "Unauthorized"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1
shellHelper.show@src/mongo/shell/utils.js:781:19
shellHelper@src/mongo/shell/utils.js:671:15
@(shellhelp2):1:1
> use admin #验证,因为在admin下面添加的帐号,所以要到admin下面验证。

switched to db admin
> db.auth('dba','dba')
1
> show dbs;
admin 0.000GB
local 0.000GB
#2、重启数据库

mongod --remove
mongod --config "E:softMongoDBetcmongod.cfg" --bind_ip 0.0.0.0 --install --auth 
#3、登录:注意使用双引号而非单引号 mongo --port 27017 -u "root" -p "123" --authenticationDatabase "admin" 也可以在登录之后用db.auth("账号","密码")登录 mongo use admin db.auth("root","123")

 3.3 基本数据类型

  • 1)在概念上,MongoDB的文档与Javascript的对象相近,因而可以认为它类似于JSON。JSON(http://www.json.org)是一种简单的数据表示方式:其规范仅用一段文字就能描述清楚(其官网证明了这点),且仅包含六种数据类型。
  • 2)这样有很多好处:易于理解、易于解析、易于记忆。然而从另一方面说,因为只有null、布尔、数字、字符串、数字和对象这几种数据类型,所以JSON的表达能力有一定的局限。
  • 3)虽然JSON具备的这些类型已经具有很强的表现力,但绝大数应用(尤其是在于数据库打交道时)都还需要其他一些重要的类型。例如,JSON没有日期类型,这使得原本容易日期处理变得烦人。另外,JSON只有一种数字类型,无法区分浮点数和整数,更别区分32位和64位了。再者JSON无法表示其他一些通用类型,如正则表达式或函数。
  • 4)MongoDB在保留了JSON基本键/值对特性的基础上,添加了其他一些数据类型。在不同的编程语言下,这些类型的确切表示有些许差异。下面说明了MongoDB支持的其他通用类型,以及如何正在文档中使用它们
#1、null:用于表示空或不存在的字段
d={'x':null}
#2、布尔型:true和false
d={'x':true,'y':false}
#3、数值
d={'x':3,'y':3.1415926}
#4、字符串
d={'x':'egon'}
#5、日期
d={'x':new Date()}
d.x.getHours()
#6、正则表达式
d={'pattern':/^egon.*?nb$/i}

正则写在//内,后面的i代表:
i 忽略大小写
m 多行匹配模式
x 忽略非转义的空白字符
s 单行匹配模式

#7、数组
d={'x':[1,'a','v']}

#8、内嵌文档
user={'name':'egon','addr':{'country':'China','city':'YT'}}
user.addr.country

#9、对象id:是一个12字节的ID,是文档的唯一标识,不可变
d={'x':ObjectId()}
  • 5)_id和ObjectId
MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任意类型,默认是个ObjectId对象。在一个集合里,每个文档都有唯一的“_id”,确保集合里每个文档都能被唯一标识。不同集合"_id"的值可以重复,但同一集合内"_id"的值必须唯一

#1、ObjectId
ObjectId是"_id"的默认类型。因为设计MongoDb的初衷就是用作分布式数据库,所以能够在分片环境中生成唯一的标识符非常重要,而常规的做法:在多个服务器上同步自动增加主键既费时又费力,这就是MongoDB采用ObjectId的原因。
ObjectId采用12字节的存储空间,是一个由24个十六进制数字组成的字符串
    0|1|2|3|   4|5|6|     7|8    9|10|11    
    时间戳      机器      PID    计数器
如果快速创建多个ObjectId,会发现每次只有最后几位有变化。另外,中间的几位数字也会变化(要是在创建过程中停顿几秒)。这是ObjectId的创建方式导致的。
时间戳单位为秒,与随后5个字节组合起来,提供了秒级的唯一性。这4个字节隐藏了文档的创建时间,绝大多数驱动程序都会提供一个方法,用于从ObjectId中获取这些信息。
因为使用的是当前时间,很多用户担心要对服务器进行时钟同步。其实没必要,因为时间戳的实际值并不重要,只要它总是不停增加就好。接下来3个字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以保证不同主机生成不同的ObjectId,不产生冲突。
接下来几个字节确保了在同一台机器上并发的多个进程产生的ObjectId是唯一的
前9个字节确保了同一秒钟不同机器不同进程产生的ObjectId是唯一的。最后3个字节是一个自动增加的 计数器。确保相同进程的同一秒产生的ObjectId也是不一样的。
#2、自动生成_id
如果插入文档时没有"_id"键,系统会自帮你创建一个。可以由MongoDb服务器来做这件事。
但通常会在客户端由驱动程序完成。这一做法非常好地体现了MongoDb的哲学:能交给客户端驱动程序来做的事情就不要交给服务器来做。
这种理念背后的原因是:即便是像MongoDB这样扩展性非常好的数据库,扩展应用层也要比扩展数据库层容易的多。将工作交给客户端做就减轻了数据库扩展的负担。

 3.4 数据库操作

#1、增
#MongoDB 创建数据库的语法格式如下:
use DATABASE_NAME#如果数据库不存在,则创建数据库,否则切换到指定数据库。
use config#创建config数据库
#2、查 
show dbs #查看所有数据库,可以发现,我们刚创建的数据库config并不在数据库的列表中, 要显示它,我们需要向config数据库插入一些数据。 
db.table1.insert({'a':1})
#3、删 
use config #先切换到要删的库下
db.dropDatabase() #删除当前库
#mongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。

3.5 集合操作

#1、增
当第一个文档插入时,集合就会被创建
> use database1
switched to db database1
> db.table1.insert({'a':1})
WriteResult({ "nInserted" : 1 })
> db.table2.insert({'b':2})
WriteResult({ "nInserted" : 1 })

#2、查
> show tables
table1
table2

#3、删
> db.table1.drop()
true
> show tables
table2

3.6 文档操作

3.6.1 增 

#1、没有指定_id则默认ObjectId,_id不能重复,且在插入后不可变

#2、插入单条
user0={
    "name":"egon",
    "age":10,
    'hobbies':['music','read','dancing'],
    'addr':{
        'country':'China',
        'city':'BJ'
    }
}

db.test.insert(user0)
db.test.find()

#3、插入多条
user1={
    "_id":1,
    "name":"alex",
    "age":10,
    'hobbies':['music','read','dancing'],
    'addr':{
        'country':'China',
        'city':'weifang'
    }
}

user2={
    "_id":2,
    "name":"wupeiqi",
    "age":20,
    'hobbies':['music','read','run'],
    'addr':{
        'country':'China',
        'city':'hebei'
    }
}


user3={
    "_id":3,
    "name":"yuanhao",
    "age":30,
    'hobbies':['music','drink'],
    'addr':{
        'country':'China',
        'city':'heibei'
    }
}

user4={
    "_id":4,
    "name":"jingliyang",
    "age":40,
    'hobbies':['music','read','dancing','tea'],
    'addr':{
        'country':'China',
        'city':'BJ'
    }
}

user5={
    "_id":5,
    "name":"jinxin",
    "age":50,
    'hobbies':['music','read',],
    'addr':{
        'country':'China',
        'city':'henan'
    }
}
db.user.insertMany([user1,user2,user3,user4,user5])

3.6.2 查

  1)比较运算

# SQL:=,!=,>,<,>=,<=
# MongoDB:{key:value}代表什么等于什么,"$ne","$gt","$lt","gte","lte",其中"$ne"能用于所有数据类型

#1、select * from db1.user where name = "alex";
db.user.find({'name':'alex'})

#2、select * from db1.user where name != "alex";
db.user.find({'name':{"$ne":'alex'}})

#3、select * from db1.user where id > 2;
db.user.find({'_id':{'$gt':2}})

#4、select * from db1.user where id < 3;
db.user.find({'_id':{'$lt':3}})

#5、select * from db1.user where id >= 2;
db.user.find({"_id":{"$gte":2,}})

#6、select * from db1.user where id <= 2;
db.user.find({"_id":{"$lte":2}})
View Code

   2)逻辑运算

# SQL:and,or,not
# MongoDB:字典中逗号分隔的多个条件是and关系,"$or"的条件放到[]内,"$not"

#1、select * from db1.user where id >= 2 and id < 4;
db.user.find({'_id':{"$gte":2,"$lt":4}})

#2、select * from db1.user where id >= 2 and age < 40;
db.user.find({"_id":{"$gte":2},"age":{"$lt":40}})

#3、select * from db1.user where id >= 5 or name = "alex";
db.user.find({
    "$or":[
        {'_id':{"$gte":5}},
        {"name":"alex"}
        ]
})

#4、select * from db1.user where id % 2=1;
db.user.find({'_id':{"$mod":[2,1]}})

#5、上题,取反
db.user.find({'_id':{"$not":{"$mod":[2,1]}}})
View Code

  3)成员运算

# SQL:in,not in
# MongoDB:"$in","$nin"

#1、select * from db1.user where age in (20,30,31);
db.user.find({"age":{"$in":[20,30,31]}})

#2、select * from db1.user where name not in ('alex','yuanhao');
db.user.find({"name":{"$nin":['alex','yuanhao']}})
View Code

  4)正则匹配

# SQL: regexp 正则
# MongoDB: /正则表达/i

#1、select * from db1.user where name regexp '^j.*?(g|n)$';
db.user.find({'name':/^j.*?(g|n)$/i})

  5)取指定字段

#1、select name,age from db1.user where id=3;
db.user.find({'_id':3},{'_id':0,'name':1,'age':1})

  6)查询数组

#1、查看有dancing爱好的人
db.user.find({'hobbies':'dancing'})

#2、查看既有dancing爱好又有tea爱好的人
db.user.find({
    'hobbies':{
        "$all":['dancing','tea']
        }
})

#3、查看第4个爱好为tea的人
db.user.find({"hobbies.3":'tea'})

#4、查看所有人最后两个爱好
db.user.find({},{'hobbies':{"$slice":-2},"age":0,"_id":0,"name":0,"addr":0})

#5、查看所有人的第2个到第3个爱好
db.user.find({},{'hobbies':{"$slice":[1,2]},"age":0,"_id":0,"name":0,"addr":0})

> db.blog.find().pretty()
{
        "_id" : 1,
        "name" : "alex意外死亡的真相",
        "comments" : [
                {
                        "name" : "egon",
                        "content" : "alex是谁???",
                        "thumb" : 200
                },
                {
                        "name" : "wxx",
                        "content" : "我去,真的假的",
                        "thumb" : 300
                },
                {
                        "name" : "yxx",
                        "content" : "吃喝嫖赌抽,欠下两个亿",
                        "thumb" : 40
                },
                {
                        "name" : "egon",
                        "content" : "xxx",
                        "thumb" : 0
                }
        ]
}
db.blog.find({},{'comments':{"$slice":-2}}).pretty() #查询最后两个
db.blog.find({},{'comments':{"$slice":[1,2]}}).pretty() #查询1到2
View Code

  7)排序

# 排序:--1代表升序,-1代表降序
db.user.find().sort({"name":1,})
db.user.find().sort({"age":-1,'_id':1})

  8)分页

# 分页:--limit代表取多少个document,skip代表跳过前多少个document。 
db.user.find().sort({'age':1}).limit(1).skip(2)

  9)计数

db.user.count({'age':{"$gt":30}}) 

--或者
db.user.find({'age':{"$gt":30}}).count()

  10)其他

#1、{'key':null} 匹配key的值为null或者没有这个key
db.t2.insert({'a':10,'b':111})
db.t2.insert({'a':20})
db.t2.insert({'b':null})

> db.t2.find({"b":null})
{ "_id" : ObjectId("5a5cc2a7c1b4645aad959e5a"), "a" : 20 }
{ "_id" : ObjectId("5a5cc2a8c1b4645aad959e5b"), "b" : null }

#2、查找所有
db.user.find() #等同于db.user.find({})
db.user.find().pretty()

#3、查找一个,与find用法一致,只是只取匹配成功的第一个
db.user.findOne({"_id":{"$gt":3}})

3.6.3 改

#################################update()#########################################
#update() 方法用于更新已存在的文档。语法格式如下:
db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)
参数说明:对比update db1.t1 set name='EGON',sex='Male' where name='egon' and age=18;

query : 相当于where条件。
update : update的对象和一些更新的操作符(如$,$inc...等,相当于set后面的
upsert : 可选,默认为false,代表如果不存在update的记录不更新也不插入,设置为true代表插入。
multi : 可选,默认为false,代表只更新找到的第一条记录,设为true,代表更新找到的全部记录。
writeConcern :可选,抛出异常的级别。

更新操作是不可分割的:若两个更新同时发送,先到达服务器的先执行,然后执行另外一个,不会破坏文档。
#注意:除非是删除,否则_id是始终不会变的
#实例1、覆盖式更新:
db.user.update({'age':20},{"name":"Wxx","hobbies_count":3})
是用{"_id":2,"name":"Wxx","hobbies_count":3}覆盖原来的记录

#实例2、一种最简单的更新就是用一个新的文档完全替换匹配的文档。这适用于大规模式迁移的情况。例如
var obj=db.user.findOne({"_id":2})

obj.username=obj.name+'SB'
obj.hobbies_count++
delete obj.age

db.user.update({"_id":2},obj)

#####################################设置:$set########################################## 
#设置:$set

通常文档只会有一部分需要更新。可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。
更新修改器是种特殊的键,用来指定复杂的更新操作,比如修改、增加后者删除

#1、update db1.user set  name="WXX" where id = 2
db.user.update({'_id':2},{"$set":{"name":"WXX",}})

#2、没有匹配成功则新增一条{"upsert":true}
db.user.update({'_id':6},{"$set":{"name":"egon","age":18}},{"upsert":true})

#3、默认只改匹配成功的第一条,{"multi":改多条}
db.user.update({'_id':{"$gt":4}},{"$set":{"age":28}})
db.user.update({'_id':{"$gt":4}},{"$set":{"age":38}},{"multi":true})

#4、修改内嵌文档,把名字为alex的人所在的地址国家改成Japan
db.user.update({'name':"alex"},{"$set":{"addr.country":"Japan"}})

#5、把名字为alex的人的地2个爱好改成piao
db.user.update({'name':"alex"},{"$set":{"hobbies.1":"piao"}})

#6、删除alex的爱好,$unset
db.user.update({'name':"alex"},{"$unset":{"hobbies":""}})

############################################增加和减少:$inc################################################
#增加和减少:$inc

#1、所有人年龄增加一岁
db.user.update({},
    {
        "$inc":{"age":1}
    },
    {
        "multi":true
    }
    )
#2、所有人年龄减少5岁
db.user.update({},
    {
        "$inc":{"age":-5}
    },
    {
        "multi":true
    }
    )
##############################################添加删除数组内元素#####################################################
#添加删除数组内元素
    
往数组内添加元素:$push
#1、为名字为yuanhao的人添加一个爱好read
db.user.update({"name":"yuanhao"},{"$push":{"hobbies":"read"}})

#2、为名字为yuanhao的人一次添加多个爱好tea,dancing
db.user.update({"name":"yuanhao"},{"$push":{
    "hobbies":{"$each":["tea","dancing"]}
}})

按照位置且只能从开头或结尾删除元素:$pop
#3、{"$pop":{"key":1}} 从数组末尾删除一个元素

db.user.update({"name":"yuanhao"},{"$pop":{
    "hobbies":1}
})

#4、{"$pop":{"key":-1}} 从头部删除
db.user.update({"name":"yuanhao"},{"$pop":{
    "hobbies":-1}
})

#5、按照条件删除元素,:"$pull" 把符合条件的统统删掉,而$pop只能从两端删
db.user.update({'addr.country':"China"},{"$pull":{
    "hobbies":"read"}
},
{
    "multi":true
}
)
 ##############################################避免添加重复:"$addToSet####################################################

#避免添加重复:"$addToSet"

db.urls.insert({"_id":1,"urls":[]})

db.urls.update({"_id":1},{"$addToSet":{"urls":'http://www.baidu.com'}})
db.urls.update({"_id":1},{"$addToSet":{"urls":'http://www.baidu.com'}})
db.urls.update({"_id":1},{"$addToSet":{"urls":'http://www.baidu.com'}})

db.urls.update({"_id":1},{
    "$addToSet":{
        "urls":{
        "$each":[
            'http://www.baidu.com',
            'http://www.baidu.com',
            'http://www.xxxx.com'
            ]
            }
        }
    }
)
 ##########################################其他###########################################

#1、了解:限制大小"$slice",只留最后n个

db.user.update({"_id":5},{
    "$push":{"hobbies":{
        "$each":["read",'music','dancing'],
        "$slice":-2
    }
    }
})

#2、了解:排序The $sort element value must be either 1 or -1"
db.user.update({"_id":5},{
    "$push":{"hobbies":{
        "$each":["read",'music','dancing'],
        "$slice":-1,
        "$sort":-1
    }
    }
})

#注意:不能只将"$slice"或者"$sort"与"$push"配合使用,且必须使用"$eah"
View Code

 3.6.4 删

#1、删除多个中的第一个
db.user.deleteOne({ 'age': 8 })

#2、删除国家为China的全部
db.user.deleteMany( {'addr.country': 'China'} ) 

#3、删除全部
db.user.deleteMany({}) 

3.6.5 聚合

如果你有数据存储在MongoDB中,你想做的可能就不仅仅是将数据提取出来那么简单了;你可能希望对数据进行分析并加以利用。MongoDB提供了以下聚合工具:
#1、聚合框架
#2、MapReduce(详见MongoDB权威指南)
#3、几个简单聚合命令:count、distinct和group。(详见MongoDB权威指南)

#聚合框架:
可以使用多个构件创建一个管道,上一个构件的结果传给下一个构件。
这些构件包括(括号内为构件对应的操作符):筛选($match)、投射($project)、分组($group)、排序($sort)、限制($limit)、跳过($skip)
不同的管道操作符可以任意组合,重复使用
#-*-coding = utf-8 -*-
import pymongo

from pymongo import MongoClient
import datetime

client=MongoClient('mongodb://root:123@localhost:27017')
table=client['db1']['emp']
l=[
('egon','male',18,'20170301','老男孩驻沙河办事处外交大使',7300.33,401,1), #以下是教学部
('alex','male',78,'20150302','teacher',1000000.31,401,1),
('wupeiqi','male',81,'20130305','teacher',8300,401,1),
('yuanhao','male',73,'20140701','teacher',3500,401,1),
('liwenzhou','male',28,'20121101','teacher',2100,401,1),
('jingliyang','female',18,'20110211','teacher',9000,401,1),
('jinxin','male',18,'19000301','teacher',30000,401,1),
('成龙','male',48,'20101111','teacher',10000,401,1),

('歪歪','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门
('丫丫','female',38,'20101101','sale',2000.35,402,2),
('丁丁','female',18,'20110312','sale',1000.37,402,2),
('星星','female',18,'20160513','sale',3000.29,402,2),
('格格','female',28,'20170127','sale',4000.33,402,2),

('张野','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门
('程咬金','male',18,'19970312','operation',20000,403,3),
('程咬银','female',18,'20130311','operation',19000,403,3),
('程咬铜','male',18,'20150411','operation',18000,403,3),
('程咬铁','female',18,'20140512','operation',17000,403,3)
]

for n,item in enumerate(l):
    d={
        "_id":n,
        'name':item[0],
        'sex':item[1],
        'age':item[2],
        'hire_date':datetime.datetime.strptime(item[3],'%Y%m%d'),
        'post':item[4],
        'salary':item[5]
    }
    table.save(d)
准备练习数据
##################################################筛选: $match#############################################
{"$match":{"字段":"条件"}},可以使用任何常用查询操作符$gt,$lt,$in等

#例1、select * from db1.emp where post='teacher';
db.emp.aggregate({"$match":{"post":"teacher"}})

#例2、select * from db1.emp where id > 3 group by post;  
db.emp.aggregate(
    {"$match":{"_id":{"$gt":3}}},
    {"$group":{"_id":"$post",'avg_salary':{"$avg":"$salary"}}}
)

#例3、select * from db1.emp where id > 3 group by post having avg(salary) > 10000;  
db.emp.aggregate(
    {"$match":{"_id":{"$gt":3}}},
    {"$group":{"_id":"$post",'avg_salary':{"$avg":"$salary"}}},
    {"$match":{"avg_salary":{"$gt":10000}}}
)

################################################################投射: $project#########################################
{"$project":{"要保留的字段名":1,"要去掉的字段名":0,"新增的字段名":"表达式"}}

#1、select name,post,(age+1) as new_age from db1.emp;
db.emp.aggregate(
    {"$project":{
        "name":1,
        "post":1,
        "new_age":{"$add":["$age",1]}
        }
})

#2、表达式之数学表达式
{"$add":[expr1,expr2,...,exprN]} #相加
{"$subtract":[expr1,expr2]} #第一个减第二个
{"$multiply":[expr1,expr2,...,exprN]} #相乘
{"$divide":[expr1,expr2]} #第一个表达式除以第二个表达式的商作为结果
{"$mod":[expr1,expr2]} #第一个表达式除以第二个表达式得到的余数作为结果

#3、表达式之日期表达式:$year,$month,$week,$dayOfMonth,$dayOfWeek,$dayOfYear,$hour,$minute,$second
#例如:select name,date_format("%Y") as hire_year from db1.emp
db.emp.aggregate(
    {"$project":{"name":1,"hire_year":{"$year":"$hire_date"}}}
)

#例如查看每个员工的工作多长时间
db.emp.aggregate(
    {"$project":{"name":1,"hire_period":{
        "$subtract":[
            {"$year":new Date()},
            {"$year":"$hire_date"}
        ]
    }}}
)

#4、字符串表达式
{"$substr":[字符串/$值为字符串的字段名,起始位置,截取几个字节]}
{"$concat":[expr1,expr2,...,exprN]} #指定的表达式或字符串连接在一起返回,只支持字符串拼接
{"$toLower":expr}
{"$toUpper":expr}

db.emp.aggregate( {"$project":{"NAME":{"$toUpper":"$name"}}})

#5、逻辑表达式
$and
$or
$not
其他见Mongodb权威指南

#######################################################分组:group#################################################

{"$group":{"_id":分组字段,"新的字段名":聚合操作符}}

#1、将分组字段传给$group函数的_id字段即可
{"$group":{"_id":"$sex"}} #按照性别分组
{"$group":{"_id":"$post"}} #按照职位分组
{"$group":{"_id":{"state":"$state","city":"$city"}}} #按照多个字段分组,比如按照州市分组

#2、分组后聚合得结果,类似于sql中聚合函数的聚合操作符:$sum、$avg、$max、$min、$first、$last
#例1:select post,max(salary) from db1.emp group by post; 
db.emp.aggregate({"$group":{"_id":"$post","max_salary":{"$max":"$salary"}}})

#例2:去每个部门最大薪资与最低薪资
db.emp.aggregate({"$group":{"_id":"$post","max_salary":{"$max":"$salary"},"min_salary":{"$min":"$salary"}}})

#例3:如果字段是排序后的,那么$first,$last会很有用,比用$max和$min效率高
db.emp.aggregate({"$group":{"_id":"$post","first_id":{"$first":"$_id"}}})

#例4:求每个部门的总工资
db.emp.aggregate({"$group":{"_id":"$post","count":{"$sum":"$salary"}}})

#例5:求每个部门的人数
db.emp.aggregate({"$group":{"_id":"$post","count":{"$sum":1}}})

#3、数组操作符
{"$addToSet":expr}:不重复
{"$push":expr}:重复

#例:查询岗位名以及各岗位内的员工姓名:select post,group_concat(name) from db1.emp group by post;
db.emp.aggregate({"$group":{"_id":"$post","names":{"$push":"$name"}}})
db.emp.aggregate({"$group":{"_id":"$post","names":{"$addToSet":"$name"}}})
#####################排序$sort、限制$limit、跳过$skip####################################
{"$sort":{"字段名":1,"字段名":-1}} #1升序,-1降序
{"$limit":n} 
{"$skip":n} #跳过多少个文档

#例1、取平均工资最高的前两个部门
db.emp.aggregate(
{
    "$group":{"_id":"$post","平均工资":{"$avg":"$salary"}}
},
{
    "$sort":{"平均工资":-1}
},
{
    "$limit":2
}
)
#例2、
db.emp.aggregate(
{
    "$group":{"_id":"$post","平均工资":{"$avg":"$salary"}}
},
{
    "$sort":{"平均工资":-1}
},
{
    "$limit":2
},
{
    "$skip":1
}
)
######################################随机选取n个:$sample ###########################################
#集合users包含的文档如下
{ "_id" : 1, "name" : "dave123", "q1" : true, "q2" : true }
{ "_id" : 2, "name" : "dave2", "q1" : false, "q2" : false  }
{ "_id" : 3, "name" : "ahn", "q1" : true, "q2" : true  }
{ "_id" : 4, "name" : "li", "q1" : true, "q2" : false  }
{ "_id" : 5, "name" : "annT", "q1" : false, "q2" : true  }
{ "_id" : 6, "name" : "li", "q1" : true, "q2" : true  }
{ "_id" : 7, "name" : "ty", "q1" : false, "q2" : true  }

#下述操作时从users集合中随机选取3个文档
db.users.aggregate(
   [ { $sample: { size: 3 } } ]
)
聚合详解

 3.6.6 综合练习

1. 查询岗位名以及各岗位内的员工姓名
2. 查询岗位名以及各岗位内包含的员工个数
3. 查询公司内男员工和女员工的个数
4. 查询岗位名以及各岗位的平均薪资、最高薪资、最低薪资
5. 查询男员工与男员工的平均薪资,女员工与女员工的平均薪资
6. 查询各岗位内包含的员工个数小于2的岗位名、岗位内包含员工名字、个数
7. 查询各岗位平均薪资大于10000的岗位名、平均工资
8. 查询各岗位平均薪资大于10000且小于20000的岗位名、平均工资
9. 查询所有员工信息,先按照age升序排序,如果age相同则按照hire_date降序排序
10. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列
11. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列,取前1个
1. 查询岗位名以及各岗位内的员工姓名
db.emp.aggregate(
{"$group":{"_id":"$post","names":{"$push":"$name"}}}
)
2. 查询岗位名以及各岗位内包含的员工个数
db.emp.aggregate(
        {
            "$group":{"_id":"$post","emp_num":{"$sum":1}}
        }
    )
3. 查询公司内男员工和女员工的个数
db.emp.aggregate(
    {
        "$group":{"_id":"$sex","num":{"$sum":1}}
    }
)
4. 查询岗位名以及各岗位的平均薪资、最高薪资、最低薪资
db.emp.aggregate(
    {
    "$group":{
        "_id":"$post",
        "avg_salary":{"$avg":"$salary"},
        "max_salary":{"$max":"$salary"},
        "min_salary":{"$min":"$salary"}
            }
    }
)
5. 查询男员工与男员工的平均薪资,女员工与女员工的平均薪资

db.emp.aggregate(
    {
        "$group":{
            "_id":"$sex",
            "avg_salary":{"$avg":"$salary"}
        
        }
    }
)

6. 查询各岗位内包含的员工个数小于2的岗位名、岗位内包含员工名字、个数
db.emp.aggregate(
    {"$group":{
            "_id":"$post",
            "names":{"$push":"$name"},
            "nums":{"$sum":1}    
        }
    },
    {"$match":{"nums":{"$lt":2}}}
)
7. 查询各岗位平均薪资大于10000的岗位名、平均工资
db.emp.aggregate(
    {"$group":{"_id":"$post","avg_salary":{"$avg":"$salary"}}},
    {"$match":{"avg_salary":{"$gt":10000}}}
)
8. 查询各岗位平均薪资大于10000且小于20000的岗位名、平均工资

db.emp.aggregate(
    {"$group":{"_id":"$post","avg_salary":{"$avg":"$salary"}}},
    {"$match":{"avg_salary":{"$gt":10000,"$lt":20000}}}
)

9. 查询所有员工信息,先按照age升序排序,如果age相同则按照hire_date降序排序
db.emp.find{}.sort({"age":1,"hire_date":-1})
或者:
db.emp.aggregate(
{"$sort":{"age":1,"hire_date":-1}}

10. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列

db.emp.aggregate(
    {"$group":{"_id":"$post","avg_salary":{"$avg":"$salary"}}},
    {"$match":{"avg_salary":{"$gt":10000}}},
    {"$sort":{"avg_salary":1}}
)

11. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列,取前1个

db.emp.aggregate(
    {"$group":{"_id":"$post","avg_salary":{"$avg":"$salary"}}},
    {"$match":{"avg_salary":{"$gt":10000}}},
    {"$sort":{"avg_salary":-1}},
    {"$limit":1}
)
View Code

 4. pymongo基本使用

1)pymongo基本使用

#-*-coding = utf-8 -*-
import pymongo
from pymongo import MongoClient
"""
MongoDB存储
    在这里我们来看一下Python3下MongoDB的基本操作,在开始之前请确保你已经安装好了MongoDB并启动了其服务,另外安装好了Python
    的PyMongo库。
"""
#1.连接MongoDB,创建client

client= MongoClient('mongodb://root:123@localhost:27017/')
#client = MongoClient(host='localhost', port=27017)
# client = MongoClient('mongodb://localhost:27017/')
#2.操作数据库

# 列出当前所有数据库名称
database_names = client.database_names()
# 使用现有数据库,假设已存在数据库,数据库名称为 test_database
test_database = client.test_database
# 创建不存在的数据库并使用
new_database = client.new_database
# 删除现有数据库
client.drop_database('new_database')
#3.操作集合

# 使用一个已存在数集合,假设集合名称为 posts
posts = test_database.posts
# 创建一个新的集合
new_collection = test_database.new_collection
# 查看当前数据库中的所有集合
collection_names = test_database.collection_names()
# 删除一个集合
test_database.drop_collection('new_collection')
#当我们创建一个新集合时,如果我们只是创建集合,但并没有向集合中添加数据,这是新的集合其实并没有创建
#4.向集合中添加文档

#pymongo 提供两种添加新文档的方法,分别是 insert_one 和 insert_many。
# insert_one(insert_data) 是将一个文档添加到集合中
# insert_many(insert_datas) 是将多个文档一次性添加到集合中。
# insert_one() 用法
conn= MongoClient('mongodb://root:123@localhost:27017/')
db = conn.mydb   # 指定数据库名称,连接mydb数据库,没有则自动创建
my_set=db.test_set  #使用test_set集合,没有则自动创建
#插入数据
student = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}

result = my_set.insert_one(student)
print(result)#<pymongo.results.InsertOneResult object at 0x0000015858C22A48>
print(result.inserted_id)#5ca3393d13b5a711d0d68f1e
# insert_one()返回的是InsertOneResult对象,我们可以调用其inserted_id属性获取_id。
# insert_many() 用法
student1 = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}

student2 = {
    'id': '20170202',
    'name': 'Mike',
    'age': 21,
    'gender': 'male'
}
result = my_set.insert_many([student1, student2])
print(result)#<pymongo.results.InsertManyResult object at 0x0000019CFD730A88>
print(result.inserted_ids)#[ObjectId('5ca33aec13b5a7d9847b2e2f'), ObjectId('5ca33aec13b5a7d9847b2e30')]
# insert_many()方法返回的类型是InsertManyResult,调用inserted_ids属性可以获取插入数据的_id列表

#5.删除集合中的文档

# 删除指定文档
foo.remove({'_id': 2})
for data in foo.find():
  print(data)
# 删除所有文档
foo.remove()
foo.count()
#6.更新文档数据

user = test_database.user
joe = {'name': 'joe', 'friends': 32, 'enemies': 2}
user.insert_one(joe)
print(user.find_one())
#接下来,我们将对这个文档作出几处修改,修改如下:
#- 将 friedns 和 enemies 移动到 relationships 子文档之下
#- 将字段 name 更改为 username
joe = user.find_one({'name': 'joe'})#{'_id': ObjectId('5ca315c413b5a7ccc825fa29'), 'name': 'joe', 'friends': 32, 'enemies': 2}
joe['relationships'] = {'firends': joe['friends'], 'enemies': joe['enemies']}
joe['username'] = joe['name']
del joe['name']
del joe['enemies']
del joe['friends']
print(joe)#{'_id': ObjectId('5ca315c413b5a7ccc825fa29'), 'relationships': {'firends': 32, 'enemies': 2}, 'username': 'joe'}
user.replace_one({'name': 'joe'}, joe)#替换文档我们使用了replace_one() 方法,该方法传入两个参数,第一个参数是需要被替换的文档查找条件,第二个参数是更新的文档数据。
joe = user.find_one()
View Code

 2)改

在实际中,更新文档往往是更新文档的一部分内容,在 MongoDB 中,我们可以使用更新修改器 (update modifier) 来对文档中某些字段进行更新,常用的修改器有以下几个:
−set用来指定一个字段的值,如果不存在,将创建一个新的字段      −unset 删除一个字段
−inc用来增加(或减少)一个已有键的值,如果不存在将会创建一个     −push 向已有的数组末尾添加一个元素
−addToSet避免插入重复数据                     −pull 删除元素,基于特定条件
−each遍历列表操作                         −pop 删除元素

#-*-coding = utf-8 -*-
import pymongo
from pymongo import MongoClient

client= MongoClient('mongodb://root:123@localhost:27017/')
# 先清除之前存储的 user 集合中的数据
test_database = client.test_database#指定数据库
user = test_database.user#指定集合
user.drop()#清除之前存储的 user 集合中的数据
print(user.count())
joe = {'name': 'joe',
          'age': 30,
          'sex': 'male',
          'location': 'Wisconsin' }
user.insert_one(joe)
print(user.find_one())#'_id': ObjectId('5ca31a1613b5a7e230ba2c89'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin'}
##################################$set#################################
# 为用户添加一项 favorite 的字段
user.update_one({'name': 'joe'}, {'$set' : {'favorite': 'War adn Peace'}})#{'_id': ObjectId('5ca31abd13b5a712913e5d99'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'favorite': 'War adn Peace'}
print(user.find_one())
# 将 favorite 字段的值修改为 Green Eggs and Ham
user.update_one({'name': 'joe'}, {'$set': {'favorite': 'Green Eggs and Ham'}})
print(user.find_one())#{'_id': ObjectId('5ca31af913b5a720b9485e6c'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'favorite': 'Green Eggs and Ham'}
# 将 favorite 字段的值替换为一个数组
user.update_one({'name': 'joe'}, {'$set': {'favorite': ["Cat's Cradle", "Foundation Trilogy", "Ender's Game"]}})
print(user.find_one())
#{'_id': ObjectId('5ca31b6113b5a74c14017141'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'favorite': ["Cat's Cradle", 'Foundation Trilogy', "Ender's Game"]}
# 内嵌文档修改
blog = test_database.blog # 创建一个 blog 集合
posts = {'title': 'A Blog Post', 'content': '...', 'author': {'name': 'joe', 'email': 'joe@example.com'}} # 创建一个 posts 文档
blog.insert_one(posts)
# 将作者名称字段 name 的值修改为 joe schmoe
blog.update_one({'author.name': 'joe'}, {'$set': {'author.name': 'joe schmoe'}})
#################################$unset##################################
# 删除 user 集合中 joe 的 favorite 字段
user.update_one({'name': 'joe'}, {'$unset': {'favorite': 1}})
print(user.find_one())#{'_id': ObjectId('5ca31ce713b5a7a9a8362e2d'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin'}
################################$inc#####################################
# 创建一个 games 的集合,并向集合中添加数据
games = test_database.games
games.insert_one({'game': 'pinball', 'user': 'joe'})

# 增加一个分数字段 score
games.update_one({'game': 'pinball', 'user': 'joe'}, {'$inc': {'score': 50}})
# 为 score 字段的值增加 5000
games.update_one({'game': 'pinball', 'user': 'joe'}, {'$inc': {'score': 5000}})

##############################$push######################################
# 选择 blog 数据库
blog = test_database.blog
# 添加一项评论字段 comment
blog.update_one({'title': 'A Blog Post'}, {'$push' : {'comments': {'name': 'joe', 'email': 'joe@example.com', 'content': 'nice post.'}}})

# 在添加一条由 bob 发表的评论
blog.update_one({'title': 'A Blog Post'}, {'$push' : {'comments': {'name': 'bob', 'email': 'bob@example.com', 'content': 'good post.'}}})


###########################$addToSet###################################
# 为 user 集合中的 joe 文档添加 emails 字段
user.update_one({'name': 'joe'},{'$push': {'emails': 'joe@example.com'}})
user.update_one({'name': 'joe'},{'$push': {'emails': 'joe@gmail.com'}})
print(user.find_one())#{'_id': ObjectId('5ca3207b13b5a70d60f01f7f'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@example.com', 'joe@gmail.com']}
# 使用 $addToSet 再添加一项 joe@example.com 的记录,因为存在重复,数据不会被重复添加
user.update_one({'name': 'joe'}, {'$addToSet': {'emails': 'joe@example.com'}})
print(user.find_one())#{'_id': ObjectId('5ca320eb13b5a7b9a0a4a650'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@example.com', 'joe@gmail.com']}
# 如果直接使用 $push ,记录会被重复添加
user.update_one({'name': 'joe'}, {'$push': {'emails': 'joe@example.com'}})
print(user.find_one())#{'_id': ObjectId('5ca320eb13b5a7b9a0a4a650'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@example.com', 'joe@gmail.com', 'joe@example.com']}
############################$pull####################################
# 删除 user 集合中 joe 文档重复的 emails 值 ($pull 会删除所有符合条件的记录)
user.update_one({'name': 'joe'}, {'$pull': {'emails': 'joe@example.com'}})
print(user.find_one())#{'_id': ObjectId('5ca3214413b5a7ba700c3b87'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@gmail.com']}
############################$each###################################
# 向 user 集合中 joe 文档的 emails 字段追加两个邮箱地址
user.update_one({'name': 'joe'}, {'$push': {'emails': {'$each': ['joe@example.com', 'joe@outlook.com']}}})
print(user.find_one())#{'_id': ObjectId('5ca321b013b5a713980810e9'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@gmail.com', 'joe@example.com', 'joe@outlook.com']}
###########################$pop####################################
# 删除集合 user 中 joe 文档 emails 字段的第一个邮箱地址
user.update_one({'name': 'joe'}, {'$pop': {'emails': -1}})
print(user.find_one())#{'_id': ObjectId('5ca321f713b5a712ad558568'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@example.com', 'joe@outlook.com']}
# 删除集合 user 中 joe 文档 emails 字段的最后一个邮箱地址
user.update_one({'name': 'joe'}, {'$pop': {'emails': 1}})
print(user.find_one())#{'_id': ObjectId('5ca3221c13b5a7ba8472d26b'), 'name': 'joe', 'age': 30, 'sex': 'male', 'location': 'Wisconsin', 'emails': ['joe@example.com']}
####################基于位置的数组修改器##########################
# 先清除 blog 集合中的原油数据
blog.drop()# 添加一条 posts 记录
posts = {'content': '...', 'comments': [{'comment': 'good post', 'author': 'John', 'votes': 0}, {'comment': 'i thought it was too short', 'author': 'Claire', 'votes': 3}, {'comment': 'free watches', 'auth: or': 'Alice', 'votes': -1}]}
blog.insert_one(posts)
print(blog.find_one())
# 为第一条评论 comments 的 votes 字段增加 1
post_id = blog.find_one()['_id']
blog.update_one({'_id': post_id}, {'$inc': {'comments.0.votes': 1}})
# 定位符的使用,通常在不知道具体数组位置,使用定位查询文档来匹配数组元素
blog.update_one({'comments.author': 'John'}, {'$set': {'comments.$.author': 'Jim'}})
View Code

 3)查

接下来,我们继续深入了解 Pymongo 的查询操作。主要介绍find_one() 以及 find() 两种常用方法来进行查询。

#-*-coding = utf-8 -*-
import pymongo
from pymongo import MongoClient

client= MongoClient('mongodb://root:123@localhost:27017/')
test_database = client.test_database#指定数据库
users = test_database.users#指定集合
users.drop()# 先清除之前存储的 user 集合中的数据
#为方便演示,我们会在 test_database 数据库中创建一个 users 的集合,并向其中添加三条文档记录。
joe = {'name': 'joe', 'age': 26}
mike = {'name': 'mike', 'age': 28}
jake = {'name': 'jake', 'age': 26}
# 使用 insert_many() 可以一次添加多个文档记录
users.insert_many([joe, mike, jake])
###############################find()################################
#我们使用 find() 方法,如果不传入任何参数,将返回该集合中的所有数据的一个游标,然后我们可以通过 for 来遍历游标来打印查询结果。
for data in users.find():
    print(data)
#{'_id': ObjectId('5ca329e913b5a76c24ab394d'), 'name': 'joe', 'age': 26}
#{'_id': ObjectId('5ca329e913b5a76c24ab394e'), 'name': 'mike', 'age': 28}
#{'_id': ObjectId('5ca329e913b5a76c24ab394f'), 'name': 'jake', 'age': 26}
#如果我们需要查找特定的数据,比如年龄为 28 的用户,那么我们可以给 find() 方法传入一个匹配的规则。
result = users.find({'age': 28})
print(result.count())#1
print(result.next())#{'_id': ObjectId('5ca329e913b5a76c24ab394e'), 'name': 'mike', 'age': 28}
#我们还可以同时传入多个查询条件进行查询。
result = users.find({'age': 26, 'name': 'jake'})
# 以上表达式多个条件会被解释为 AND 关系
print(result.count())
##############################find_one()############################
#find_one() 的用法与 find()的使用方法差别不大,他们的区别是使用 find_one() 最多只会返回一条文档记录,而 find() 则返回查询游标。
# find_one() 如果没有查询条件,会返回第一条记录
print(users.find_one())#{'_id': ObjectId('5ca32c0b13b5a7b08cd2bcb9'), 'name': 'joe', 'age': 26}
# 如果传入查询条件,没有查询结果,则会返回一个 NoneType
print(type(users.find_one({'name': 'kate'})))#<class 'NoneType'>
# 如果查询匹配多个结果,find_one() 只会返回第一条匹配记录
print(users.find_one({'age': 26}))#{'_id': ObjectId('5ca32c6413b5a7c804d0d2d6'), 'name': 'joe', 'age': 26}
#我们在查询的时候,可能并不需要文档中的所有字段,这时我们可以在查询条件之后再传入一个参数来指定返回的字段。
# 不要 _id 字段
print(users.find_one({}, {'_id': 0}))#{'name': 'joe', 'age': 26}
# 只输出 _id 字段
print(users.find_one({}, {'_id': 1}))#{'_id': ObjectId('5ca32cde13b5a7be78b78d78')}
#########################比较操作符############################
#在查询中,我们会经常用到比较字段值的大小来查询数据,实现这一功能我们会用到比较操作符,Pymongo 常用的比较操作符有以下几个:
#$lt:小于,$lte 小于等于, $ge 大于, $gte 大于等于, $ne 不等于
# 查询大于 26 岁的用户
for data in users.find({'age': {'$gt': 26}}):
    print(data)#{'_id': ObjectId('5ca32e0413b5a78ea083b361'), 'name': 'mike', 'age': 28}
# 查询大于等于 26 岁的用户
for data in users.find({'age': {'$gte': 26}}):
    print(data)
#{'_id': ObjectId('5ca32e3f13b5a7801cb6c32e'), 'name': 'joe', 'age': 26}
#{'_id': ObjectId('5ca32e3f13b5a7801cb6c32f'), 'name': 'mike', 'age': 28}
#{'_id': ObjectId('5ca32e3f13b5a7801cb6c330'), 'name': 'jake', 'age': 26}
##########################成员操作符############################
#我们可以使用 $in 和 $nin 操作符来匹配一个键的多个值
# 匹配 users 集合中 用户名为 joe 和 mike 的文档记录
for data in users.find({'name': {'$in': ['joe', 'mike']}}):
    print(data)
# 匹配用户名不是 mike 的用户,注意: $in 和 $nin 条件必须是一个数组
for data in users.find({'name': {'$nin': ['mike']}}):
    print(data)
View Code



>>>>>待续

原文地址:https://www.cnblogs.com/wuxunyan/p/10623152.html