MongoDB 学习笔记

MongoDB 学习笔记

感谢 Karl Seguin 编写的 The Little MongoDB Book 这本 MongoDB 入门书。

本文内容主要来自「The Little MongoDB Book」的学习,学习笔记基于个人理解对原书部分内容进行调整。

如果你是 MongoDB 数据库初学者,建议从学习「The Little MongoDB Book」 开始。

如果需要练习 MongoDB 相关命令行工具可直接阅读本学习笔记。

笔者测试 MongoDB 数据库版本较早,但文中涉及的所有 MongoDB 概念及命令行工具基本适用于所有版本。

一 简介

了解 MongoDB 前先了解 NoSQL,NoSQL 是一种数据存储系统(非关系型数据库系统),相比 MySQL 这类关系型数据库提供通用的数据存储解决方案,NoSQL 倾向负责解决系统中一部分数据存储问题。

MongoDB 是一种 NoSQL 解决方案,提供更加通用的 NoSQL 方案。

本文约定 Mongo 和 MongoDB 都是代指 MongoDB 数据库系统。

二 准备

2.1 安装和运行 Mongo 服务

在学习 MongoDB 之前,需要安装 MongoDB 环境。可到 官方下载页面 下载需要的 MongoDB 版本。

  1. Windows 操作系统可以在 这里 下载对应的 MongoDB 安装包。

  2. 解压下载的包(到任意路径)并进入 bin 子目录,暂且不要执行任何命令。让我先介绍一下,mongod 将启动服务器进程,而 mongo 会打开客户端的 shell,——大部分时间我们将和这两个可执行文件打交道。

  3. bin 子目录中创建一个新的文本文件,取名为。

  4. mongodb.config 中加一行:dbpath=PATH_TO_WHERE_YOU_WANT_TO_STORE_YOUR_DATABASE_FILES。例如,在 Windows 中您需要添加的可能是 dbpath=c:\mongodb\data,而在Linux下可能就是 dbpath=/etc/mongodb/data。

  5. 确认您指定的dbpath是存在的。

  6. 执行 mongod,带上参数--config /path/to/your/mongodb.config。命令是这样的 mongod --config /path/to/your/mongodb.config

上面的安装步骤用于安装和启动 MongoDB 服务器进程。下面以 Windows 系统为例,讲解 MongoDB 安装及启动详细过程。

  1. 将安装包解压到 c:\mongodb\
  2. 创建 c:\mongodb\data\ 文件夹,该文件夹将用于存储 MongoDB 的数据;
  3. 创建 c:\mongodb\bin\mongodb.config 配置文件并添加 dbpath=c:\mongodb\data\ 配置;
  4. 命令行中进入 c:\mongodb\bin 目录,执行 c:\mongodb\bin\mongod --config c:\mongodb\bin\mongodb.config 启动 Mongo 服务;

对于不创建 c:\mongodb\bin\mongodb.config配置的用户,可以通过 --dbpath 参数启动服务,执行 c:\mongodb\bin\mongod --dbpath c:\mongodb\data\;效果和使用 --config 参数一样。

接下来,我们通过 SHELL 环境连接 MongoDB 服务。进入 c:\mongodb\bin 目录,执行 mongo 命令,即可完成 MongoDB 服务连接。

需要说明的是 MongoDB 在 c:\mongodb\bin,提供了一些工具,其中就包括上去的 mongod.exemongo.exe,它们对应 mongodmongo 命令。

2.2 MongoDB 基本概念

如果有使用过关系型数据库(如 MySQL),那么对数据库、表、行、字段这些概念不会陌生,在 MongoDB 中也有类似的数据结构,不过在 Mongo 中将以另一种形式存在:

  1. 数据库 (database)相当于 SQL 中的 database
  2. 集合 (collection)相当于 SQL 中的 table
  3. 文档 (document)相当于 SQL 中的 row
  4. 域 (field)相当于 SQL 中的 column
  5. 索引 (index)相当于 SQL 中的 index
  6. 主键 (primaryKey)相当于 SQL 中的主键,但 MongoDB 会自动在插入(insert)数据时将 _id 域,设置为主键字段

MongoDB 和 关系型数据库相关概念关系对照表:

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

表格信息引用自 MongoDB 概念解析

2.2.1 与数据库、集合、文档相关常用命令

下文中所有命令,需在命令终端执行。当使用 mongo 命令成功连接 MongoDB 服务后,可以使用 help 获取 MongoDB 数据库使用帮助,它的执行结果大致如下:

> help
    db.help()                    help on db methods
    db.mycoll.help()             help on collection methods
    rs.help()                    help on replica set methods
    help admin                   administrative help
    help connect                 connecting to a db help
    help keys                    key shortcuts
    help misc                    misc things to know
    help mr                      mapreduce

    show dbs                     show database names
    show collections             show collections in current database
    show users                   show users in current database
    show profile                 show most recent system.profile entries with time >= 1ms
    show logs                    show the accessible logger names
    show log [name]              prints out the last segment of log in memory, 'global' is default
    use <db_name>                set current database
    db.foo.find()                list objects in collection foo
    db.foo.find( { a : 1 } )     list objects in foo where a == 1
    it                           result of the last line evaluated; use to further iterate
    DBQuery.shellBatchSize = x   set default number of items to display on shell
    exit                         quit the mongo shell
  1. 显示 MongoDB 数据库

执行 show dbs,显示 MongoDB 所有数据库

  1. 切换 MongoDB 数据库

执行 use test,将工作数据库切入到 test 数据库。在 MongoDB 中没有类似关系数据库的 Schema 概念,所以即使 MongoDB 的数据库未创建,也可以任意切换工作数据库。

  1. 显示数据库中的集合(collection)

执行 show colections,会打印输出当前数据库中的所有数据库集合。

  1. 获取工作数据库帮助信息

当使用 use test 进入 test 数据库后,可以执行 db.help() 指令查看 database 级别支持的命令,结果如下:

> db.help()
DB methods:
        db.addUser(username, password[, readOnly=false])
        db.auth(username, password)
        db.cloneDatabase(fromhost)
        db.commandHelp(name) returns the help for the command
        db.copyDatabase(fromdb, todb, fromhost)
        db.createCollection(name, { size : ..., capped : ..., max : ... } )
        db.currentOp() displays the current operation in the db
        db.dropDatabase()
        db.eval(func, args) run code server-side
        db.getCollection(cname) same as db['cname'] or db.cname
        db.getCollectionNames()
        db.getLastError() - just returns the err msg string
        db.getLastErrorObj() - return full status object
        db.getMongo() get the server connection object
        db.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair
        db.getName()
        db.getPrevError()
        db.getProfilingLevel() - deprecated
        db.getProfilingStatus() - returns if profiling is on and slow threshold
        db.getReplicationInfo()
        db.getSiblingDB(name) get the db at the same server as this one
        db.isMaster() check replica primary status
        db.killOp(opid) kills the current operation in the db
        db.listCommands() lists all the db commands
        db.logout()
        db.printCollectionStats()
        db.printReplicationInfo()
        db.printSlaveReplicationInfo()
        db.printShardingStatus()
        db.removeUser(username)
        db.repairDatabase()
        db.resetError()
        db.runCommand(cmdObj) run a database command.  if cmdObj is a string, turns it into { cmdObj : 1 }
        db.serverStatus()
        db.setProfilingLevel(level,<slowms>) 0=off 1=slow 2=all
        db.shutdownServer()
        db.stats()
        db.version() current version of the server
        db.getMongo().setSlaveOk() allow queries on a replication slave server
        db.fsyncLock() flush data to disk and lock server for backups
        db.fsyncUnock() unlocks server following a db.fsyncLock()
  1. 查看工作数据库名

需要查看当前工作的数据库,执行如下命令

> db.getName()
test1
  1. 查看工作数据库统计信息

db.stats() 会列出工作数据库中 集合、数据库大小等有用的信息。

> db.stats()
{
    "db":"test", --查看的是哪个数据库
    "collections":7, --collection数量
    "objects":28, --对象数量
    "avgObjSize":50.57142857142857, --对象平均大小
    "dataSize":1416, --数据大小
    "storageSize":31744, --数据大小(含预分配空间)
    "numExtents":7, --事件数量
    "indexes":7, --索引数量
    "indexSize":57344, --索引大小
    "fileSize":50331648, --文件大小
    "ok":1 --本次取stats是否正常
}

上例结果引用自 db.stats() - 显示当前db状态

  1. 删除数据库

当需要删除 某个 数据库时,请先使用 use 指令切换工作数据库至待删除数据库。然后执行 db.dropDatabase() 指令。

> show dbs
    local   (empty)
    test    0.03125GB
    test1   (empty)

> use test1
    switched to db test1

> db.dropDatabase()
{ "dropped" : "test1", "ok" : 1 }

> show dbs
local   (empty)
test    0.03125GB

有了上面的基本概念,我们就知道如何使用 MongoDB 的数据库和数据集了,下面是一个小练习,来加深相关知识的使用及理解:

2.2.2 MongoDB 训练场

场景:我们开辟一个训练场,将完成数据库服务启动、客户端连接 MongoDB 服务、查看和选择数据库及数据库删除操作。

约定在 shell 中以 -- 开始的注释说明信息:执行时请不要赋值注释信息。

-- 1. 启动一个 Windows 命令行窗口,开启 MongoDB 服务

> cd c:\mongodb\bin
> mongod --config c:\mongodb\bin\mongodb.config

-- 2. 另启动一个 Windows 命令行窗口连接 MongoDB 服务
> cd c:\mongodb\bin
> mongo

MongoDB shell version: 2.0.7
connecting to: test

-- 3. 查看所有数据库
> show dbs

local   (empty)
test    0.03125GB
test1   (empty)

-- 4. 切换工作数据库至 blog
> use blog

switched to db blog

-- 5. 查看当前工作数据库

> db.getName()

blog

-- 6. 切换工作数据库至 test
> use test

switched to db test

-- 7. 查看当前工作数据库

> db.getName()

test

-- 8. 切回工作数据库至 blog
> use blog

switched to db blog

-- 9. 查看 blog 库的所有集合, 由于是空数据库所以会没有返回信息
> show collections

-- 10 查看 blog 库的状态,由于是空数据库所以统计信息内相关数据为空

> db.stats()
{
        "db" : "blog",
        "collections" : 0,
        "objects" : 0,
        "avgObjSize" : 0,
        "dataSize" : 0,
        "storageSize" : 0,
        "numExtents" : 0,
        "indexes" : 0,
        "indexSize" : 0,
        "fileSize" : 0,
        "nsSizeMB" : 0,
        "ok" : 1
}

-- 11. 插入一个用户到 blog 数据库的 user 集合里。
-- 不知道如何插入?没关系,直接复制下面的命令就好了。
> db.user.insert({name: 'huliuqing', age: 18, hobby: ['coding', 'reading']})

-- 12. 查看刚刚 user 集合的插入结果
> db.user.find()
{ "_id" : ObjectId("5abde35e7d318c10d73539e3"), "name" : "huliuqing", "age" : 18, "hobby" : [ "coding", "reading" ] }

-- 13. 再看下 blog 库的状态
> db.stats()
{
        "db" : "blog",
        "collections" : 3,
        "objects" : 5,
        "avgObjSize" : 51.2,
        "dataSize" : 256,
        "storageSize" : 16384,
        "numExtents" : 3,
        "indexes" : 1,
        "indexSize" : 8176,
        "fileSize" : 16777216,
        "nsSizeMB" : 16,
        "ok" : 1
}

-- 14. 删除 user 集合
-- 不知道使用什么命令?那先来查看下 user 集合层级的帮助信息吧
> db.user.help()
...
    db.user.drop() drop the collection
...

> db.user.drop()
true

-- 14. 再看下 blog 库的状态
{
        "db" : "blog",
        "collections" : 2,
        "objects" : 1,
        "avgObjSize" : 36,
        "dataSize" : 36,
        "storageSize" : 8192,
        "numExtents" : 2,
        "indexes" : 0,
        "indexSize" : 0,
        "fileSize" : 16777216,
        "nsSizeMB" : 16,
        "ok" : 1
}

-- 15. 删除 blog 数据库
> db.dropDatabases()
{ "dropped" : "blog", "ok" : 1 }

-- 16. 再看下 blog 库的状态
> db.stats()
{
        "db" : "blog",
        "collections" : 0,
        "objects" : 0,
        "avgObjSize" : 0,
        "dataSize" : 0,
        "storageSize" : 0,
        "numExtents" : 0,
        "indexes" : 0,
        "indexSize" : 0,
        "fileSize" : 0,
        "nsSizeMB" : 0,
        "ok" : 1
}

-- 17 查看下数据库
> show dbs

local   (empty)
test    0.03125GB
test1   (empty)

2.3 MongoDB 基本概念总结

  1. MongoDB 有 数据库 组成;数据库由 集合 组成;集合由 文档组成;文档包含一个或多个 ;且集合可以被 索引,以提升 查找排序 效率。
  2. 在关系型数据库中在数据表(table)层级定义列(column)信息;在 NoSQL 中是在 文档 这一层定义 ,即一个集合里的每个文档都可以有自己的域。
  3. MongoDB 中的集合是无模式的(schema-less),无需提前创建(即不需要像 SQL 一样 CREATE TABLE 或 CREATE DATABASE)。
  4. 命令的分类
    • 全局命令,使用 help 查看全局命令
    • 当前数据库级别命令,执行 db.help() 命令查看
    • 当前集合级别命令,执行 db.your_collection.help() 命令查看。

另外需要说明的是 MongoDB 的 SHELL 运行于 JavaScript 之上。除全局命令外,操作 db 数据库、 db.COLLECTION_NAME 操作集合,若缺少 () 将会在 SHELL 里打印出方法的实现源码。

三 MongoDB CRUD 操作

前面我们学习了如何使用 MongoDB 的帮助,接下来在学习 CRUD 操作之前,进一步了解下 MongoDB 的数据库及集合是如何创建的。

MongoDB 是 无模式 的,当使用 use YOUR_DATABASE 命令切换数据库时,我们无需预先创建 YOUR_DATABASE 数据库,而当向某个 集合 插入一个 文档 时,将会自动生成具体的数据库、集合和文档。

如:

> use mongo_playground
> db.users.insert({name: 'huliuqing', age: 18, gender: 'male'})

上面的命令对 users 集合做 insert(插入文档) 操作,传入的参数是一个 JSON 格式数据。通过 show collections 命令查看到有: userssystem.indexes 两个集合存在,其中 system.indexes 集合会在每个 数据库MongoDB 自主创建,这个集合包含数据库中的索引信息。

在执行 insert 命令时,MongoDB 会生成一个值为 ObjectId 类型的 _id 域_id 域 对每个 文档 都是必须的,它类似于 SQL 的主键,我们可以使用自己的算法生成 _id 的值,大部分情况下使用 MongoDB 的默认值就可以了。

前面说过 _id 域 类似主键,它的索引信息被存储在 system.indexes 集合内,我们看看两个集合里有什么数据:


-- 1. 查看集合
> show collections

system.indexes
users

-- 2. 查看 users 集合数据
> db.users.find().pretty()
{
        "_id" : ObjectId("5ac2f7ecfdcd54e4d368bde5"),
        "name" : "huliuqing",
        "age" : 18,
        "gender" : "male"
}

-- 3. 查看 system.indexes 集合数据
> db.system.indexes.find().pretty()
{
        "v" : 1,
        "key" : {
                "_id" : 1
        },
        "ns" : "mongo_playground.users",
        "name" : "_id_"
}

从结果我们可以看到 users 集合比添加的 JSON 多了 _id 域,它的索引信息被 system.indexes 集合记录。

3.1 CRUD - CREATE 创建 MongoDB 数据

创建一个 文档db.YOUR_COLLECTION.insert() 命令完成,我们向 db.users 集合插入一条新的文档:

-- 1. 创建文档
> db.users.insert({name: 'zhangsanfeng', age: 120, gender: 'male', hobby: ['Kung fu', 'Tai Chi']});

-- 2. 查询结果
> db.users.find()
{ "_id" : ObjectId("5ac2f7ecfdcd54e4d368bde5"), "name" : "huliuqing", "age" : 18, "gender" : "male" }
{ "_id" : ObjectId("5ac3165bfdcd54e4d368bde6"), "name" : "zhangsanfeng", "age" : 120, "gender" : "male", "hobby" : [ "Kung fu", "Tai Chi" ] }

3.2 CRUD - RETRIEVE 查询 MongoDB 数据

一个简单的查询操作可以使用 db.YOUR_COLLECTION.find() 指令来获取所有 YOUR_COLLECTION 集合的所有文档列表。除此之外,我们还需要知道在 MongoDB 中有个 查询构造器 的概念,查询构造器 类似于 SQL 中的 WHERE 语句

查询构造器

在学习查询构造器之前,我们先清洗下 mongo_playground 数据库,并加入测试集合。

-- 1. 进入 mongo_playground 数据库
> use mongo_playground

-- 2. 删除 mongo_playground 数据库
> db.dropDatabase()

-- 3. 创建测试数据
db.users.insert({name: 'Bob',birthday: new Date(1992,2,13,7,47),hobby: ['basketball','football'],weight: 60,gender: 'm',age: 18});
db.users.insert({name: 'John',birthday: new Date(1991, 0, 24, 13, 0),hobby: ['basketball', 'ping pong'],weight: 45,gender: 'm',age: 23});
db.users.insert({name: 'Tony',birthday: new Date(1973, 1, 9, 22, 10),hobby: ['boxing', 'racing'],weight: 98,gender: 'm',age: 30});
db.users.insert({name: 'Lily',birthday: new Date(1997, 6, 1, 10, 42),hobby: ['ping pong', 'yoga'],weight: 69,gender: 'f',age: 39});
db.users.insert({name: 'Jack',birthday: new Date(1979, 7, 18, 18, 44),hobby: ['skiing'],weight: 57,gender: 'm',age: 51});
db.users.insert({name: 'Tom',birthday: new Date(1985, 6, 4, 2, 1),hobby:['skiing', 'basketball','boxing'],weight:65,gender:'m',age:29});
db.users.insert({name: 'Jackson',birthday: new Date(1998, 2, 7, 8, 30),hobby: ['skateboard', 'running'],weight: 73,gender: 'f',age: 41});
db.users.insert({name: 'Lucy',birthday: new Date(2005, 4, 3, 0, 57),hobby: ['skiing', 'yoga'],weight: 42,gender: 'f',age: 22});
db.users.insert({name: 'Peter',birthday: new Date(2001, 9, 8, 14, 53),hobby: ['shooting', 'darts'],weight: 60,gender: 'm',age: 48});
db.users.insert({name: 'Rose',birthday: new Date(1997, 2, 1, 5, 3),hobby: ['shooting', 'darts'],weight: 65,gender: 'f',age: 56});
db.users.insert({name: 'Lee',birthday: new Date(1999, 11, 20, 16, 15),hobby: ['ping pong', 'basketball'],weight: 54,gender: 'm'});

3.2.1 条件查询

3.2.1.1 基础条件查询 {fieldName: value}

查找用户 name 等于 Lee 的信息

> db.users.find({name: 'Lee'}).pretty()
{
        "_id" : ObjectId("5ac31e00fdcd54e4d368bdfc"),
        "name" : "Lee",
        "birthday" : ISODate("1999-12-20T08:15:00Z"),
        "hobby" : [
                "ping pong",
                "basketball"
        ],
        "weight" : 54,
        "gender" : "m"
}

3.2.1.2 查询 {fieldName1:value1, fieldName2:value2}

查找喜欢 跑步男性

> db.users.find({gender: 'm', hobby: 'racing'}).pretty()
{
        "_id" : ObjectId("5ac31e00fdcd54e4d368bdf4"),
        "name" : "Tony",
        "birthday" : ISODate("1973-02-09T14:10:00Z"),
        "hobby" : [
                "boxing",
                "racing"
        ],
        "weight" : 98,
        "gender" : "m",
        "age" : 30
}

3.2.1.3 查询 {$or: [{fieldName: value1}, {fieldName: value2}]}

查找喜欢 乒乓球瑜伽男性

-- 1. 仅查找喜欢 乒乓球 或 瑜伽 的用户
> db.users.find({gender: 'm', $or: [{hobby: 'ping pong'}, {hobby: 'yoga'}]})

{ "_id" : ObjectId("5ac327edfdcd54e4d368be09"), "name" : "John", "birthday" : ISODate("1991-01-24T05:00:00Z"), "hobby" : [ "basketball", "ping pong" ], "weight" : 45, "gender" : "m", "age" : 23 }
{ "_id" : ObjectId("5ac327edfdcd54e4d368be12"), "name" : "Lee", "birthday" : ISODate("1999-12-20T08:15:00Z"), "hobby" : [ "ping pong", "basketball" ], "weight" : 54, "gender" : "m" }

-- 2. 下面的查找将查找到所有的喜欢 乒乓球 或 瑜伽 的用户
> db.users.find({$or: [{hobby: 'ping pong'}, {hobby: 'yoga'}]})

3.2.1.4 比较查询

通过 $lt(less than: 小于)$lte(less than and equal: 小于等于)$gt(greater than: 大于)$gte(greater than and equal: 大于等于)$ne(not equal: 不等于) 等实现比较查询。

-- 1. 查询年龄大于等于 30 岁且小于  40 岁的用户
> db.users.find({age: {$gte: 30, $lt: 40}})

-- 2. 查询年龄大于 55 岁的用户
> db.users.find({age: {$gt: 55}})

-- 3. 查询年龄小于 18 岁的用户
> db.users.find({age: {$lt: 20}})

-- 4. 查询年龄不等于 18 岁的用户
> db.users.find({age: {$ne: 18}})

3.2.1.5 判断文档中是否存在某个域

3.2.1.4 的第 4 个示例 4. 查询年龄不等于 18 岁的用户 会查询到没有 age 域的用户 Lee。通过 $exists 指令可以判断某个域是否存在,它的值是 bool 类型,我们对这个示例做些改进:

-- 1. 查询年龄字段存在且年龄不等于 18 岁的用户
> db.users.find({age: {$ne: 18, $exists: true}})

3.2 总结

本章了解了 MongoDB 相关查询操作,了解更多查询命令细节可以查看 查询文档。但更重要的是反复练习这些查询语句的基本用法。

3.3 CRUD - UPDATE 更新 MongoDB 数据

MongoDB 的更新需要特别关注一下,更新数据使用 db.YOUR_COLLECTIONS.update(query, object, options) 方法,update 接收两个必选参数:查询选择器需要更新的域

3.3.1 UPDATE 覆盖 与 UPDATE $set 更新

一个简单的示例,我们找到 年龄 48 岁 的用户 Peter,将他的年龄 更新为 49 岁

> db.users.find({age: 48}).pretty()
{
        "_id" : ObjectId("5ac42ff514c16270040db426"),
        "name" : "Peter",
        "birthday" : ISODate("2001-10-08T06:53:00Z"),
        "hobby" : [
                "shooting",
                "darts"
        ],
        "weight" : 60,
        "gender" : "m",
        "age" : 48
}

> db.users.update({age: 48}, {age: 49})

> db.users.find({age: 49}).pretty()
{ "_id" : ObjectId("5ac42ff514c16270040db426"), "age" : 49 }

再次查找 年龄 49 岁 的用户,会发现 更新后 Perter 用户数据被 覆盖{ "_id" : ObjectId("5ac42ff514c16270040db426"), "age" : 49 }(因为 ObjectId 相同)。

这是因为: 在 MongoDB 中接收的第二个参数,如果没有使用 $set 修饰符,将会采取 覆盖 文档操作,而不是 更新文档指定域,这和 SQL 的 UPDATE 语句行为不一样。

正确的更新 应该是下面的 update 写法使用 {$set: {age: 49}},操作之前我们要清空并重建测试数据。

> db.users.find({age: 48}).pretty()
{
        "_id" : ObjectId("5ac42ff514c16270040db426"),
        "name" : "Peter",
        "birthday" : ISODate("2001-10-08T06:53:00Z"),
        "hobby" : [
                "shooting",
                "darts"
        ],
        "weight" : 60,
        "gender" : "m",
        "age" : 48
}

> db.users.update({age: 48}, {$set: {age: 49}})

> db.users.find({age: 49}).pretty()
{
        "_id" : ObjectId("5ac4375b14c16270040db43c"),
        "name" : "Peter",
        "birthday" : ISODate("2001-10-08T06:53:00Z"),
        "hobby" : [
                "shooting",
                "darts"
        ],
        "weight" : 60,
        "gender" : "m",
        "age" : 49
}

建议: 在执行 删除更新 这类操作前,建议先采用相同的查询条件查找数据,结果与判断一致时再做 删除更新 等操作。

3.3.2 UPDATE $inc 和 $push 修饰符

  • $inc 修饰符,对文档中的某个域增加一个 正值负值
  • $push 修饰符,向域的值为数组中添加新值。
-- 1. Peter 又长大了一岁
> db.users.find({name: 'Peter'})
{ "_id" : ObjectId("5ac4375b14c16270040db43c"), "name" : "Peter", "birthday" : ISODate("2001-10-08T06:53:00Z"), "hobby" : [ "shooting", "darts" ], "weight" : 60, "gender" : "m", "age" : 49 }
> db.users.update({name: 'Peter'}, {$inc: {age: 1}})

-- 2. Peter 越活越年轻
> db.users.find({name: 'Peter'})
{ "_id" : ObjectId("5ac4375b14c16270040db43c"), "name" : "Peter", "birthday" : ISODate("2001-10-08T06:53:00Z"), "hobby" : [ "shooting", "darts" ], "weight" : 60, "gender" : "m", "age" : 50 }

> db.users.update({name: 'Peter'}, {$inc: {age: -5}})

-- 3. 因为 Peter 有了更多的兴趣爱好
> db.users.update({name: 'Peter'}, {$push: {hobby: 'racing'}})
{ "_id" : ObjectId("5ac4375b14c16270040db43c"), "age" : 45, "birthday" : ISODate("2001-10-08T06:53:00Z"), "gender" : "m", "hobby" : [ "shooting", "darts", "racing" ], "name" : "Peter", "weight" : 60 }

3.3.3 UPDATE upsert 选项: 插入新文档或更新文档域数据

MongoDB update 方法第三个参数接收 bool 值的标识符,该值默认为 false。当该值设为 true 时若 查询选择器 的目标文档存在,则采取 update $set 域 操作;若不存在则采取 INSERT 操作。

这个选项在类似 网站点击计数器 统计场景中非常有用。如果网页点击记录存在则更新记录数,不存在则执行插入操作。

-- 1. 给 users 页面统计点击数, update 第三个参数为 false 或 缺省,将不会创建 hits 集合
> db.hits.update({page: 'users'}, {$inc: {hits: 1}})
> db.hits.find()

-- 2. upsert 选项设置为 true,在执行 update 更新操作时,hits 集合未创建,执行创建操作
> db.hits.update({page: 'users'}, {$inc: {hits: 1}}, {upsert: true})
> db.hits.find()
{ "_id" : ObjectId("5ac4427bb5b4350bc6d2f715"), "hits" : 1, "page" : "users" }

-- 3. upsert 选项设置为 true,在执行 update 更新操作时,hits 集合已创建,执行更新操作
> db.hits.update({page: 'users'}, {$inc: {hits: 1}}, {upsert: true})
> db.hits.find()
{ "_id" : ObjectId("5ac4427bb5b4350bc6d2f715"), "hits" : 2, "page" : "users" }

3.3.4 批量更新

MongoDB update 操作在 3.3.1 介绍过,更新数据需要 $set 修饰符,否则执行覆盖操作。这里我们介绍它的第二个独特特性:默认更新一条记录

当我们需要给所有用户加上点赞数 likes 域用于记录用户得到的赞,我们会想下面的方法一样执行,但仅仅只有一条文档加上了 likes 域:

> db.users.update({}, {$set:{likes: 0}})
> db.users.find({likes: 0})

multi 选项值设为 true 可以实现所有文档记录新增 操作。

> db.users.update({}, {$set:{likes: 0}}, false, true)
> db.users.find({likes: 0})

可以在文档查看 update 方法相关具体使用。

3.4 CRUD - DELETE 删除 MongoDB 数据

通过 db.YOUR_COLLECTION.remove(query, justOne) 可以删除一个或所有 文档,参数接收的查询选择器为空时删除所有文档,当 justOne 标识为 true 是仅删除一条匹配文档。

-- 1. 删除用户 Bob 的记录
> db.users.find({name: 'Bob'})
> db.users.remove({name: 'Bob'})
> db.users.find({name: 'Bob'})

-- 2. 删除所有用户
> db.users.find()
> db.users.remove()
> db.users.find()

相关细节可以查阅 删除文档

如果需要删除所有文档,我们还可以通过 db.YOUR_COLLECTIONS.drop() 方法实现,drop() 方法不仅删除所有文档还会删除该集合的索引信息。

3.5 CRUD - 重识 RETRIEVE 查询 MongoDB 数据

我们在 3.2 章节中了解了有关 MongoDB 的查询功能。本节我们将学习包括查询指定域、排序、返回的结果集记录数限制和分页等功能,这些方法在应用程序开发过程中会十分常见。

3.5.1 返回指定域

在 Mongo Shell 里我们通过 db.YOUR_COLLECTION.find 注意 无 () 可以看到 find 方法的具体实现,find 一共可以接收 4 个参数:第一个参数是查询选择器;第二个参数即为指定返回的域。

之前,我们都是返会所有的域信息,现在让我们仅返回用户的用户名、年龄和性别:

> db.users.find({}, {name: true, age: true, gender: true})
{ "_id" : ObjectId("5acada245c193d1acc967575"), "name" : "Bob", "gender" : "m", "age" : 18 }
{ "_id" : ObjectId("5acada245c193d1acc967576"), "name" : "John", "gender" : "m", "age" : 23 }
{ "_id" : ObjectId("5acada245c193d1acc967577"), "name" : "Tony", "gender" : "m", "age" : 30 }
...

默认 _id 域总会作为查询结果返回,可以设置 {_id: falsee} 显示的排除掉。

3.5.2 排序

在 MongoDB 中我们还需要了解一个基本概念 游标(cursor),由于前面我们并没有涉及到游标的使用(只是看起来没有涉及到游标)。

find 方法返回的结果即为依据查询选择器匹配到的文档集合的 游标,这样可以通过链式操作对 find 结果集进行处理。

我们在 MongoDB Shell 里输入 db.users.help() 命令可以看到下面的帮助信息:

> db.users.help()
...
        db.users.find([query],[fields]) - query is an optional query filter. fields is optional set of fields to return.
                                                      e.g. db.users.find( {x:77} , {name:1, x:1} )
        db.users.find(...).count()
        db.users.find(...).limit(n)
        db.users.find(...).skip(n)
        db.users.find(...).sort(...)
...

countlimitskipsort 等方法即是作用在 find 返回的游标上。

下面我们依据用户的年龄按照 升序降序 排列:

-- 1. 依据升序排列
> db.users.find({}, {name: 1, age: 1}).sort({age: 1})

-- 2. 依据降序排列
> db.users.find({}, {name: 1, age: 1}).sort({age: -1})

3.5.3 分页

要实现分页功能需要结合使用 skip(n)limit(n) 两个方法,它们的返回值也是一个游标。skip 会选择游标的起始位置,limit 类似 SQL 的 limit 语句表示返回的结果集最大纪录条数。

下面我们查下年龄第二和第三大的用户:

-- 1. 先看下年龄最大的 5 名用户
> db.users.find({}, {name: true, age: 1}).sort({age: -1}).limit(5)

-- 2. 再看下年龄第二和第三大的用户
> db.users.find({}, {name: true, age: 1}).sort({age: -1}).skip(1).limit(2)

3.5.4 查询集合记录数

在 MongoDB 中可以直接使用 db.YOUR_COLLECTION.count() 方法获取集合记录数,也可以通过 db.YOUR_COLLECTION.find().count() 获取:

获取年龄大于等于 20 岁的用户数:

> db.users.count({age: {$gte: 20}})
> db.users.find({age: {$gte: 20}}).count()

但是要知道 db.YOUR_COLLECTION.count() 仅仅是 db.YOUR_COLLECTION.find().count() 的别名,在它的内部还是调用 db.YOUR_COLLECTION.find().count()

> db.users.count
function (x) {
    return this.find(x).count();
}

更多有关作用于游标的方法可以查阅 游标方法

TO BE CONTINUE

发表评论

电子邮件地址不会被公开。 必填项已用*标注