2.1 数据库操作

默认保留的数据库

  • admin: 从权限角度考虑, 这是 root 数据库, 如果将一个用户添加到这个数据库, 这个用户自动继承所有数据库的权限, 一些特定的服务器端命令也只能从这个数据库运行, 比如列出所有的数据库或者关闭服务器
  • local: 数据永远不会被复制, 可以用来存储限于本地的单台服务器的集合 (部署集群, 分片等)
  • config: Mongo 用于分片设置时, config 数据库在内部使用, 用来保存分片的相关信息
    > show dbs
    admin   0.000GB
    config  0.000GB
    local   0.000GB
    > use articledb
    switched to db articledb
    > show dbs
    admin   0.000GB
    config  0.000GB
    local   0.000GB
    复制代码

当我们创建了一个数据库后再进行查看会发现,我们创建的数据库并没有显示出来,这是由于MongoDD的存储机制决定的

当使用 use articledb 的时候. articledb 其实存放在内存之中, 当 articledb 中存在一个 collection 之后, mongo 才会将这个数据库持久化到硬盘之中.

javascript copyable" lang="JavaScript">> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
> use articledb
switched to db articledb
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
> db.articledb.insertOne({"a": 3})
{
        "acknowledged" : true,
        "insertedId" : ObjectId("62e128b6a70e7344a5139207")
}
> show dbs
admin      0.000GB
articledb  0.000GB
config     0.000GB
local      0.000GB
复制代码

另外: 数据库名可以是满足以下条件的任意UTF-8字符串。

  • 不能是空字符串("")。
  • 不得含有' '空格)、.$/0 (空字符)。
  • 应全部小写。
  • 最多64字节。

集合操作与数据库操作类似,这里不再单独演示

2.2 文档基本 CRUD

官方文档: docs.mongodb.com/manual/crud…

2.2.1 创建 Create

Create or insert operations add new documents to a collection. If the collection does not currently exist, insert operations will create the collection automatically.

文档的数据结构和 JSON 基本一样。

所有存储在集合中的数据都是 BSON 格式。

BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称

  • 使用 db..insertOne() 向集合中添加一个文档, 参数一个 json 格式的文档 -db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:
    db.collection.insertOne(
     ,
     {
        writeConcern: 
     }
    )
    复制代码
  • 使用 db..insertMany() 向集合中添加多个文档, 参数为 json 文档数组 db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:
db.collection.insertMany(
   [ 1> , 2>, ... ],
   {
      writeConcern: ,
      ordered: <boolean>
   }
)
复制代码

参数说明:

  • document:要写入的文档。
  • writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
  • ordered:指定是否按顺序写入,默认 true,按顺序写入

我们平时使用最多的只有document这一个字段

#  插入单条数据

> var document = db.collection.insertOne({"a": 3})
> document
{
        "acknowledged" : true,
        "insertedId" : ObjectId("571a218011a82a1d94c02333")
}

#  插入多条数据
> var res = db.collection.insertMany([{"b": 3}, {'c': 4}])
> res
{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("571a22a911a82a1d94c02337"),
                ObjectId("571a22a911a82a1d94c02338")
        ]
}
复制代码

还可以通过js函数方式批量插入文档:

1、先创建数组 2、将数据放在数组中 3、一次 insert 到集合中

var arr = [];

for(var i=1 ; i<=20000 ; i++){
    arr.push({num:i});
}

db.numbers.insert(arr);
复制代码

注:当我们向 collection 中插入 document 文档时, 如果没有给文档指定 _id 属性, 那么数据库会为文档自动添加 _id field, 并且值类型是 ObjectId(blablabla), 就是文档的唯一标识, 类似于 relational database 里的 primary key

  • mongo 中的数字, 默认情况下是 double 类型, 如果要存整型, 必须使用函数 NumberInt(整型数字), 否则取出来就有问题了
  • 插入当前日期可以使用 new Date()

如果某条数据插入失败, 将会终止插入, 但已经插入成功的数据不会回滚掉. 因为批量插入由于数据较多容易出现失败, 因此, 可以使用 try catch 进行异常捕捉处理, 测试的时候可以不处理.如:

try {
// 插入多条记录
db.comment.insertMany([
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},
{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},
{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);

} catch (e) {
  print (e);
}
复制代码

2.2.2 查询 Read

更多查询可以看2.4节和2.5节

  • 使用 db..find() 方法对集合进行查询, 接受一个 json 格式的查询条件. 返回的是一个数组
  • db..findOne() 查询集合中符合条件的第一个文档, 返回的是一个对象

// 插入多条记录
> db.comment.insertMany([
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},
{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},
{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);
{
        "acknowledged" : true,
        "insertedIds" : [
                "1",
                "2",
                "3",
                "4",
                "5"
        ]
}

// 只返回查询到的第一条数据
> db.comment.findOne({"articleid":"100001"})
{
        "_id" : "1",
        "articleid" : "100001",
        "content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。",
        "userid" : "1002",
        "nickname" : "相忘于江湖",
        "createdatetime" : ISODate("2019-08-05T22:08:15.522Z"),
        "likenum" : 1000,
        "state" : "1"
}
// 等价于
db.comment.find({"articleid":"100001"}).limit(1)
复制代码

如果我们不需要那么多的字段,我们可以在查询条件后面再跟上需要查询的字段,1表示显示指定的字段,其中_id是默认显示的,我们指定0表示强制不显示

// 只显示articleid字段
> db.comment.find({"articleid":"100001"},{"articleid":1}).limit(1)
{ "_id" : "1", "articleid" : "100001" }
// 强制_id不显示
> db.comment.find({"articleid":"100001"},{"articleid":1,"_id":0}).limit(1)
{ "articleid" : "100001" }
复制代码

可以使用 $in 操作符表示范围查询

db.inventory.find( { status: { $in: [ "A", "D" ] } } )
复制代码

多个查询条件用逗号分隔, 表示 AND 的关系

db.inventory.find( { status: "A", qty: { $lt: 30 } } )
复制代码

等价于下面 sql 语句

SELECT * FROM inventory WHERE status = "A" AND qty < 30
复制代码

使用 $or 操作符表示后边数组中的条件是OR的关系

db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )
复制代码

等价于下面 sql 语句

SELECT * FROM inventory WHERE status = "A" OR qty < 30
复制代码

联合使用 ANDOR 的查询语句

db.inventory.find( {
     status: "A",
     $or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )
复制代码

在 terminal 中查看结果可能不是很方便, 所以我们可以用 pretty() 来帮助阅读

db.inventory.find().pretty()
复制代码

匹配内容

db.posts.find({
  comments: {
    $elemMatch: {
      user: 'Harry Potter'
    }
  }
}).pretty()

// 正则表达式
db..find({ content : /once/ })
复制代码

创建索引

db.posts.createIndex({
  { title : 'text' }
})

// 文本搜索
// will return document with title "Post One"
// if there is no more posts created
db.posts.find({
  $text : {
    $search : ""Post O""
  }
}).pretty()
复制代码

2.2.3 更新 Update

  • 使用 db..updateOne(, , ) 方法修改一个匹配 条件的文档
  • 使用 db..updateMany(, , ) 方法修改所有匹配 条件的文档
  • 使用 db..replaceOne(, , ) 方法替换一个匹配 条件的文档
  • db..update(查询对象, 新对象) 默认情况下会使用新对象替换旧对象

其中 参数与查询方法中的条件参数用法一致

覆盖修改,会将其他的值清除

// nModified1表示有一条记录被修改
> db.comment.update({"_id":"1"}, {"likenum":NumberInt(1001)})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
// 可以看到其他字段的值不见了
> db.comment.find()
{ "_id" : "1", "likenum" : 1001 }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 888, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一直喝凉开水,冬天夏天都喝。", "userid" : "1004", "nickname" : "杰克船长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T11:01:02.521Z"), "likenum" : 3000, "state" : "1" }
复制代码

局部修改,只修改我们修改的部分,其他字段不受影响

如果需要修改指定的属性, 而不是替换需要用“修改操作符”来进行修改

  • $set 修改文档中的制定属性
// 发现局部修改后其他字段并不受影响
> db.comment.update({ "_id": "2" }, {$set:{ "likenum": NumberInt(1001) }})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.comment.find()
{ "_id" : "1", "likenum" : 1001 }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 1001, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一直喝凉开水,冬天夏天都喝。", "userid" : "1004", "nickname" : "杰克船长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T11:01:02.521Z"), "likenum" : 3000, "state" : "1" }
复制代码

其中最常用的修改操作符即为$set$unset,分别表示赋值取消赋值.

db.inventory.updateOne(
    { item: "paper" },
    {
        $set: { "size.uom": "cm", status: "P" },
        $currentDate: { lastModified: true }
    }
)

db.inventory.updateMany(
    { qty: { $lt: 50 } },
    {
        $set: { "size.uom": "in", status: "P" },
        $currentDate: { lastModified: true }
    }
)
复制代码
  • uses the $set operator to update the value of the size.uom field to "cm" and the value of the status field to "P",
  • uses the $currentDate operator to update the value of the lastModified field to the current date. If lastModified field does not exist, $currentDate will create the field. See $currentDate for details.

db..replaceOne() 方法替换除 _id 属性外的所有属性, 其参数应为一个全新的文档.

db.inventory.replaceOne(
    { item: "paper" },
    { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 40 } ] }
)
复制代码

批量修改

在后面添加{multi: true}即可

// 默认会修改第一条
db.commnet.update({ userid: "30", { $set {username: "guest"} } })

// 修改所有符合条件的数据
db.commnet.update( { userid: "30", { $set {username: "guest"} } }, {multi: true} )
复制代码

列值增长的修改

如果我们想实现对某列值在原有值的基础上进行增加或减少, 可以使用 $inc 运算符来实现

db.commnet.update({ _id: "3", {$inc: {likeNum: NumberInt(1)}} })
复制代码

修改操作符

2.2.4 删除 Delete

  • db.collection.remove()通过添加删除规则进行删除
  • 使用 db.collection.deleteMany() 方法删除所有匹配的文档.
  • 使用 db.collection.deleteOne() 方法删除单个匹配的文档.
  • db.collection.drop()
  • db.dropDatabase()

只删除一条记录

如果不加后面的限制会删除所有匹配的记录

以下语句可以将数据全部删除,请慎用

db.comment.remove({})
复制代码

Delete operations do not drop indexes, even if deleting all documents from a collection.

一般数据库中的数据都不会真正意义上的删除, 会添加一个字段, 用来表示这个数据是否被删除

2.3 文档排序和投影 (sort & projection)

2.3.1 排序 Sort

在查询文档内容的时候, 默认是按照 _id 进行排序

我们可以用 $sort 更改文档排序规则

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
复制代码

For the field or fields to sort by, set the sort order to 1 or -1 to specify an ascending or descending sort respectively, as in the following example:

db.users.aggregate(
   [
     { $sort : { age : -1, posts: 1 } }
     // ascending on posts and descending on age
   ]
)
复制代码

$sort Operator and Memory

$sort + $limit Memory Optimization

When a $sort precedes a $limit and there are no intervening stages that modify the number of documents, the optimizer can coalesce the $limit into the $sort. This allows the $sort operation to only maintain the top n results as it progresses, where n is the specified limit, and ensures that MongoDB only needs to store n items in memory. This optimization still applies when allowDiskUse is true and the n items exceed the aggregation memory limit.

Optimizations are subject to change between releases.

有点类似于用 heap 做 topK 这种问题, 只维护 k 个大小的 heap, 会加速 process

举个栗子:

db.posts.find().sort({ title : -1 }).limit(2).pretty()
复制代码

2.3.2 投影 Projection

有些情况, 我们对文档进行查询并不是需要所有的字段, 比如只需要 id 或者 用户名, 我们可以对文档进行“投影”

  • 1 - display
  • 0 - dont display
> db.users.find( {}, {username: 1} )

> db.users.find( {}, {age: 1, _id: 0} )
复制代码

2.4 分页查询

2.4.1 统计查询

统计查询使用count()方法,语法如下:

db.collection.count(query, options)
复制代码

参数:

提示: 可选项暂时不使用。

【示例】

(1)统计所有记录数: 统计comment集合的所有的记录数:

(2)按条件统计记录数:例如:统计userid为1003的记录条数

提示: 默认情况下 count() 方法返回符合条件的全部记录条数。

2.4.2 分页列表查询

可以使用limit()方法来读取指定数量的数据,使用skip()方法来跳过指定数量的数据

基本语法如下所示:

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
复制代码

2.4.3 排序查询

sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用 于降序排列。

语法如下所示:

db.COLLECTION_NAME.find().sort({KEY:1}) 
或 
db.集合名称.find().sort(排序方式)
复制代码

例如: 对userid降序排列,并对访问量进行升序排列

提示: skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit(),和命令编写顺序无关。

2.5 其他查询方式

2.5.1 正则表达式(模糊查询)

MongoDB的模糊查询是通过正则表达式的方式实现的。格式为

$ db.collection.find({field:/正则表达式/})

$ db.collection.find({字段:/正则表达式/})
复制代码

提示:正则表达式是js的语法,直接量的写法。 例如,我要查询评论内容包含“开水”的所有文档,代码如下:

如果要查询评论的内容中以“专家”开头的,代码如下:

附录:常用的正则表达式

2.5.2 比较查询

<, <=, >, >= 这些操作符也是很常用的, 格式如下:

其实这些字符就是对应JS里面的:gt(great than)、lt(less than)、gte(great than equal )、lte(less than equal )、ne(not equal)

db.collection.find({ "field" : { $gt: value }}) // 大于: field > value
db.collection.find({ "field" : { $lt: value }}) // 小于: field < value
db.collection.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.collection.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.collection.find({ "field" : { $ne: value }}) // 不等于: field != value
复制代码

示例:查询评论点赞数量大于700的记录

2.5.3 包含查询

包含使用 $in 操作符. 示例:查询评论的集合中 userid 字段包含 10031004的文档

db.comment.find({userid:{$in:["1003","1004"]}})
复制代码

不包含使用 $nin 操作符. 示例:查询评论集合中 userid 字段不包含 10031004 的文档

db.comment.find({userid:{$nin:["1003","1004"]}})
复制代码

2.5.4 条件连接查询

我们如果需要查询同时满足两个以上条件,需要使用$and操作符将条件进行关联。(相当于SQL的and) 格式为:

$and:[ { },{ },{ } ]
复制代码

示例:查询评论集合中likenum大于等于700 并且小于2000的文档:

db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})
复制代码

如果两个以上条件之间是或者的关系,我们使用 操作符进行关联,与前面 and的使用方式相同 格式为:

$or:[ { },{ },{ } ]
复制代码

示例:查询评论集合中userid为1003,或者点赞数小于1000的文档记录

db.comment.find({$or:[ {userid:"1003"} ,{likenum:{$lt:1000} }]})
复制代码

2.5.5 foreach查询

我们知道这些查询语句其实就是js的语法格式,所有在查询得到结果后我们也可以通过forEach函数对结果进行遍历

db.posts.find().forEach(
    fucntion(doc) { 
        print('Blog Post: ' + doc.title) 
    })
// 也可以通过箭头函数简化一下
db.comment.find().forEach((it)=> { 
      print(it._id)
});
复制代码

2.5.6 地理位置查询

请查看MongoDB中文文档:地理空间查询 - MongoDB-CN-Manual (mongoing.com)

2.6 常用命令小结

选择切换数据库:use articledb
插入数据:db.comment.insert({bson数据})
查询所有数据:db.comment.find();
条件查询数据:db.comment.find({条件})
查询符合条件的第一条记录:db.comment.findOne({条件})
查询符合条件的前几条记录:db.comment.find({条件}).limit(条数)
查询符合条件的跳过的记录:db.comment.find({条件}).skip(条数)

修改数据:db.comment.update({条件},{修改后的数据})
        或
        db.comment.update({条件},{$set:{要修改部分的字段:数据})

修改数据并自增某字段值:db.comment.update({条件},{$inc:{自增的字段:步进值}})

删除数据:db.comment.remove({条件})
统计查询:db.comment.count({条件})
模糊查询:db.comment.find({字段名:/正则表达式/})
条件比较运算:db.comment.find({字段名:{$gt:值}})
包含查询:db.comment.find({字段名:{$in:[值1, 值2]}})
        或
        db.comment.find({字段名:{$nin:[值1, 值2]}})

条件连接查询:db.comment.find({$and:[{条件1},{条件2}]})
           或
           db.comment.find({$or:[{条件1},{条件2}]})
复制代码

3. 文档间的对应关系

  • 一对一 (One To One)
  • 一对多/多对一(one to many / many to one)
  • 多对多 (Many To Many)

作者:是小梁同学呀
来源:稀土掘金