0%

mongodb 入门-CRUD

文档主键

文档主键 _id

文档主键 _id 是每篇文档必备的字段,具有以下特性:

  • 文档主键的唯一性
  • 支持所有数据类型(数组除外)
  • 复合主键

对象主键 ObjectId

当我们不提供主键,MongoDB 自动为我们生成的默认对象主键 ObjectId

  • 默认的文档主键
  • 可以快速生成的 12 字节 id
  • 包含创建时间

ObjectId 默认值

1
2
test> ObjectId()
ObjectId("64eeed51b64b9d7e95e6b8ea")

手动设置 ObjectId 的值

1
2
test> ObjectId("123456789011123456789011")
ObjectId("123456789011123456789011")

提取 ObjectId 的创建时间

1
2
test> ObjectId("123456789011123456789011").getTimestamp()
ISODate("1979-09-05T22:51:36.000Z")

复合主键

使用文档作为文档主键

举个例子:

1
2
3
4
5
db.accounts.insert({
_id: {accountNo: "001", type: "savings"},
name: "irene",
balance: 100
})

复合主键仍然要满足文档主键的唯一性,需要字段值和顺序完全一致才算重复。

下面是不同的 _id:

1
_id: {accountNo: "001", type: "savings"}
1
_id: { type: "savings", accountNo: "001"}

创建文档

创建单个文档

1
2
3
4
5
6
db.<collection>.insertOne(
<document>,
{
writeConcern: <document>
}
)
  • <collection>: 文档集合名
  • <document>: 准备写入数据库的文档
    • Json 文档格式
      1
      2
      3
      4
      5
      {
      _id: "account1",
      name: "alice",
      balance: 100
      }
  • writeConcern: 文档定义了本次文档创建操作的安全写级别简单来说,安全写级别用来判断一次数据库写入操作是否成功。安全写级别越高,丢失数据的风险就越低,然而写入操作的延迟也可能更高。如果不提供writeConcern 文档,MongoDB 使用默认的安全写级别

举个例子:

1
2
3
4
5
6
7
db.accounts.insertOne(
{
_id: "account1",
name: "alice",
balance: 100
}
)
  • insertOneinsetManyinsert 会自动创建相应集合

  • _id 字段不能重复,省略创建文档中的 _id 字段,会自动生成 _id

创建多个文档

1
2
3
4
5
6
7
db.<collection>.insertMany(
[<document1>, <document2>, ...],
{
writeConcern: <document>,
ordered: <boolean>
}
)
  • ordered: MongoDB 是否按照顺序来写入这些文档。默认为 true

举个例子:

1
2
3
4
5
6
db.accounts.insertMany(
[
{name: "charlie", balance: 500},
{name: "david", balance: 200}
]
)

错误处理

顺序写入时,一旦遇到错误,操作便会退出,剩余的文档无论正确与否,都不会被写入。

乱序写入时,即使某些文档造成了错误,剩余的正确文档仍然会被写入

创建单个或多个文档

1
2
3
4
5
6
7
db.<collection>.insert(
<document or array of documents>,
{
writeConcern: <document>,
ordered: <boolean>
}
)

insert 命令既可以提供单个 document 也可以提供 document 数组, 返回结果不同, 正确返回 WriteReult 对象

举个例子:

1
2
3
4
5
6
7
db.accounts.insert(
{
name: "george",
balance: 1000
}
)

返回结果:

1
2
WriteResult({ "nInserted": 1 })
// nInserted: 插入数量

三个命令的区别

  • 返回结果不同
  • insertOneinsertMany 命令不支持 db.collection.explain() 命令,insert 命令支持。

save

1
2
3
4
5
6
db.<collection>.save(
<document>,
{
writeConcern: <document>
}
)

db.collection.save() 命令处理一个新文档的时候,会调用 db.collection.insert() 命令

读取文档

1
db.<collection>.find(<query>, <projection>)
  • <query>: 查询文档
  • <projection>: 定义了对读取结果进行的投射

读取全部文档

既不筛选,也不使用投射

1
db.accounts.find()

格式友好:

1
db.accounts.find().pretty()

筛选文档

匹配查询

1
db.accounts.find({name: "alice", balance: 100})

嵌入文档的查询:

1
db.accounts.find({"_id.type": "savings"})

比较操作符

常用的比较操作符包括:

  • $eq 匹配字段值相等的文档
  • $ne 匹配字段值不等的文档
    • 也会筛选出不包含查询字段的文档
  • $gt 匹配字段值大于查询值的文档
  • $gte 匹配字段值大于或等于查询值的文档
  • $le 匹配字段值小于查询值的文档
  • $lte 匹配字段值小于或等于查询值的文档

操作命令格式:

1
{ <field>: {$<operator>: <value> }}

举个例子:

读取 alice 的文档:

1
db.accounts.find({ name: { $eq: "alice" }})

读取余额大于 100 的文档:

1
db.accounts.find({ balance: { $gt: 100 }})
  • $in 匹配字段值与任一查询值相等的文档
  • $nin 匹配字段值与任何查询值都不相等的文档
    • 也会筛选出不包含查询字段的文档

操作命令格式:

1
{ <field>: { $in: [<value1>, <value2>, ...] }}

举个例子:

读取 alicecharlie 的银行账户文档:

1
db.accounts.find({ name: { $in: ["alice", "charlie"] }})

逻辑操作符

  • $not 匹配筛选条件不成立的文档
    • 操作命令格式:
      1
      { <field>: { $not: {<operator-expression> } }}
    • 也会筛选出不包含查询字段的文档
  • $and 匹配筛选条件全部成立的文档
    • 操作命令格式:
      1
      { $and: [{<operator-expression1>}, {<operator-expression2>}, ...]}
  • $or 匹配筛选条件至少一个成立的文档
    • 操作命令格式:
      1
      { $or: [{<operator-expression1>}, {<operator-expression2>}, ...]}
    • 当所有的筛选条件都是 $eq 操作符时,$or$in 效果一样
  • $nor 匹配多个筛选条件全部不成立的文档
    • 操作命令格式:
      1
      { $nor: [{<operator-expression1>}, {<operator-expression2>}, ...]}
    • 也会筛选出不包含查询字段的文档

举个例子:

读取余额不小于 500 的银行账户文档:

1
2
3
db.accounts.find(
{balance: {$not: {$lt: 500 }}}
)

读取余额大于 100 且用户姓名排在 fred 之后的银行账户文档:

1
2
3
4
5
6
db.accounts.find(
{ and: {
{balance: {$gt: 100}},
{name: {$lt: "fred" }}
}}
)

当筛选条件应用在不同字段上时,可以省略 $and 操作符, 上面等价于:

1
2
3
4
db.accounts.find(
{balance: {$gt: 100}},
{name: {$lt: "fred" }}
)

当筛选条件应用在同一字段上时,也可以省略 $and 操作符。例如:

查询余额大于 100 并且小于 500 的银行账户:

1
2
3
db.accounts.find(
{balance: {$gt: 100, $lt: 500 }}
)

字段操作符

  • $exists
    • 查询包含字段值的文档
    • 操作命令格式:
      1
      { field: { $exists: <boolean> }}

举个例子:
读取文档主键 _id.type 存在并且不等于 checking

1
2
3
db.accounts.find(
{"_id.type": { $ne: "checking", $exists: true }}
)
  • $type
    • 查询包含字段值类型的文档
    • 操作命令格式:
      1
      2
      { field: { $type: <BSON type> }}
      { field: { $type: [<BSON type1>, <BSON type2>, ... ]}}

举个例子:

读取文档主键是字符串的银行账户文档

1
2
3
db.accounts.find(
{_id: { $type: "string" }}
)

读取文档主键是对象主键或者是复合主键的银行账户文档

1
2
3
db.accounts.find(
{_id: { $type: ["ObjectId", "object"] }}
)

也可以使用 BSON 类型序号作为 $type 操作符的参数

1
2
3
db.accounts.find(
{_id: { $type: 2 }}
)

数组操作符

  • $all
    • 筛选数组中包含所有的查询值的文档
    • 操作命令格式:
      1
      { field: { $all: [<value1>, <value2>, ... ]}}
  • $elemMatch
    • 筛选数组中任一一个元素满足查询条件
    • 操作命令格式:
      1
      { field: { $elemMatch: {<query1>, <query2>, ... }}}

举个例子

1
2
3
db.accounts.find(
{contact: { $elemMatch: { $gt: 100, %lt: 200 }}}
)

$all 可以 与 $elemMatch 结合使用:
读取包含一个在 10002000 之间和一个在 20003000 之间的联系电话的银行账户文档:

1
2
3
4
5
6
db.accounts.find(
{contact: { $all: [
{$elemMatch:{$gt: 1000, $lt: 2000}},
{$elemMatch:{$gt: 2000, $lt: 3000}},
]}}
)

运算操作符

$regex

正则表达式,操作命令格式:

1
2
{ <field>: { : /pattern/, : '<options>'}}
{ <field>: { : /pattern/<options> }}

兼容 PCRE v8.41 正则表达式库

在和 $in 操作符一起使用时,只能使用 /pattern/<options> 方式

举个例子:

读取用户姓名以 c 或者 j 开头的银行账户文档:

1
db.accounts.find({name: {$in: [ /^c/, /^j/ ]}})

读取用户姓名包含 ALI (不区分大小写)的银行账户文档:

1
db.accounts.find({name: {$regex: /ALI/, $options: "i"}})

文档游标

db.collection.find() 返回一个文档集合游标

在不迭代游标的情况下,只列出前 20 个文档

1
test> var myCursor = db.accounts.find(); myCursor

可以使用游标下标直接访问文档集合中的某一个文档

1
test> var myCursor = db.accounts.find(); myCursor[0]

遍历完游标中所有的文档之后,或者在 10 分钟之后,游标会自动关闭, 可以使用 noCursorTimeout() 函数来保持游标一直有效

1
test> var myCursor = db.accounts.find().noCursorTimeout()

主动关闭游标

1
test> myCursor.close()

游标函数

  • cursor.hasNext()
  • cursor.next()
  • cursor.forEach()
  • cursor.limit()
    • limit(0) 相当于没有 limit
  • cursor.skip()
  • cursor.count()
    • 默认情况下 applySKipLimitfalse, 即 count 不会考虑 skiplimit 的效果
    • 不提供筛选条件时, count 会从集合的元数据 Metadata 中取得结果,当数据库分布式结构较为复杂时,元数据中的文档数量可能不正确,这时更建议使用聚合管道来计算文档数量
  • cursor.sort()
    • document 定义排序要求,1 表示正向排序, -1 表示逆向排序
    • {field: ordering}

不管命令顺序,执行的顺序永远是 sort > skip > limit

文档投影

1
db.<collection>.find(<query>, <projection>)

不使用投影时, find 返回符合条件的完整文档,而使用投影可以有选择性的返回文档中的部分字段

projection 格式:

1
{ filed: inclusion }

1 表示返回字段,0 表示不返回字段, 没有设置主键默认会返回

举个例子:

只返回银行账户的用户姓名

1
db.accounts.find({}, {name: 1})

只返回银行账户的用户姓名(不包含主键)

1
db.accounts.find({}, {name: 1, _id: 0})

除了文档主键外,不可以在投影文档上混合使用包含和不包含这两种投影操作

🙅

1
db.accounts.find({}, {name: 1, balance: 0})

数组字段上使用投影

$slice

$slice 操作符可以返回数组字段中的部分元素

1
{$slice : n| -n | [i, j]}
  • n: 数组前 n 个元素
  • -n: 数组后 n 个元素
  • [i, j]: 跳过 i 个元素,返回接下来 j 个元素

只返回 contact 数组中的第一个元素

1
db.accounts.find({}, {_id : 0, name: 1, contact: {$slice: 1}})

$eleeMatch & $

$eleeMatch$操作符返回数组中满足筛选条件的第一个元素

1
db.accounts.find({}, {_id : 0, name: 1, contact: {$elemMatch: {$gt: "Alabama"}}})

与下等同:

1
db.accounts.find({contact: { $gt: "Alabama"}}, {_id : 0, name: 1, contact: "contact.$: 1"})

更新文档

1
db.collection.update(<query>, <update>, <options>)
  • <query> 筛选条件
  • <update> 更新文档
  • <options> 设置参数

更新整篇文档

如果 <update> 文档不包含任何更新操作符,db.collection.update() 将会使用 <update>文档直接替换集合中符合 <query> 文档筛选条件的文档

1
db.account.update({name:"alice" }, {name : "alice", "balance":123})

注意问题:

  • 文档主键 _id 是不可以更改的
  • 只有第一篇符合 <query> 文档符合筛选条件的文档会被更新

更新特定字段

更新文档特定字段,需要使用到文档更新操作符,如下:

  • $set 更新或新增字段
  • $unset 删除字段
  • $rename 重命名
  • $inc 加减字段值
  • $mul 相乘字段值
  • $min 比较减小字段值
  • $max 比较增大字段值

$set

1
{ $set: { <filed1>: <value1>, ...}}

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
db.accounts.update(
{name: "jack"},
{
$set:
{
balance: 3000,
info: {
dateOpened: new Date("2016-05-18T16:00:00Z"),
branch: "branch1"
},
}
}
)

更新或新增内嵌文档的字段

1
2
3
4
5
6
7
8
9
db.accounts.update(
{name: "jack"},
{
$set:
{
"info.dateOpened": new Date("2017-01-01T16:00:00Z")
}
}
)

更新或新增数组字段

$set 中使用数组名加下标,如果向现有数组字段范围以外的位置添加新值,数组字段的长度会被扩大,未被赋值的数组成员将被设置为 null

举个例子 jackcontact 数组中默认有 3 个元素:

  • 更新 jackcontact 数组中第一个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    db.accounts.update(
    {name: "jack"},
    {
    $set:
    {
    "contact.0": "666666"
    }
    }
    )
  • 更新 jackcontact 数组中第四元素

    $set 中使用数组名加下标 3, 默认只有 3 个,这个操作会在数组后边新增一个元素.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    db.accounts.update(
    {name: "jack"},
    {
    $set:
    {
    "contact.3": "77777"
    }
    }
    )
  • 更新 jackcontact 数组中第六个元素

    $set 中使用数组名加下标 5, 现在只有 4 个,这个操作会在数组后边新增两个元素,第五个元素为 null 值,第六个为新增值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    db.accounts.update(
    {name: "jack"},
    {
    $set:
    {
    "contact.5": "99999"
    }
    }
    )

$unset

删除字段, 传入任何值都一样,默认设置为 "", 格式如下:

1
{ $unset: {field1: "", ...}}

例如删除 jack 的银行账户余额和开户地点:

1
2
3
4
5
6
7
8
9
10
db.accounts.update(
{name:"jack"},
{
$unset:
{
balance: "",
"info.branch": ""
}
}
)
  • 删除内嵌文档的字段
    • 同更新内嵌文档字段方式
  • 删除数组内的字段
    • 同更新内嵌文档字段方式。删除数组中元素成功时,这个元素不会被删除,只会被赋以 null 值,而数组的长度不会改变

如果 $unset 命令中的字段不存在,那么文档内容将保持不变

$rename

重命名文档字段

1
{ $rename: {<field1>: <newName1>, <field2>:<newName2>, ...}}
  • 如果 $rename 命令中的字段不存在,那么文档内容将保持不变
  • 如果新的字段名已经存在,那么原有的这个字段会被覆盖

$rename 命令中的新字段存在时,$rename 命令会先 $unset 旧字段,然后再 $set 新字段

  • 重命名内嵌文档的字段
    • 普通重命名字段名
      • 同更新内嵌文档字段方式
    • 更新内嵌文档中字段的位置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      db.accounts.update(
      {name: "karen" }
      {
      $rename:
      {
      "info.branch": "branch",
      "balance": "info.balance"
      }
      }
      )

$rename 命令中的旧字段和新字段都不可以指向数组元素

$inc & $mul

更新字段值, $inc 加减字段值(正负数),$mul 相乘字段值,格式如下:

1
2
3
{ $inc: { <field1> : <value1>, ...}}

{ $mul: { <field1> : <value1>, ...}}

举个例子 david 账户的 notYetExist 值加 10:

1
2
3
4
5
6
7
8
9
db.accounts.update(
{name: "david"},
{
$inc:
{
notYetExist: 10
}
}
)

使用注意:

  • $inc$mul 只能应用在数字字段上
  • 当更新字段不存在时
    • $inc 会创建字段,并且将字段值设定为命令中的增减值
    • $mul 会创建字段,但是把字段值设为 0

$min & $max

比较后更新值,$min 比较后保留较小字段值 $max 比较后保留较大字段值:

1
2
3
{ $min: { <field1> : <value1>, ...}}

{ $max: { <field1> : <value1>, ...}}

举个例子:

会将 info.balance 的当前值同 5000 比较,保留较小的值。

1
2
3
4
5
6
7
8
9
db.accounts.update(
{name: "david"},
{
$min:
{
"info.balance": 5000
}
}
)

使用注意:

  • 可以在任何可以比较大小的字段上使用
  • 当更新字段不存在时
    • $min$max 都会创建字段,并且将字段值设定为命令中的更新值
  • 被更新的字段类型和更新值类型不一致
    • $min$max会按照 BSON 数据类型排序规则进行比较。

      不同 BSON 类型的值时,MongoDB 使用以下从低到高的比较顺序:

      MinKey (internal type)
      Null
      Numbers (ints, longs, doubles, decimals)
      Symbol, String
      Object
      Array
      BinData
      ObjectId
      Boolean
      Date
      Timestamp
      Regular Expression
      MaxKey (internal type)

更新数组操作符

  • $addToSet 向数组中增添元素
  • $pop 从数组中移除元素
  • $pull 从数组中有选择性的移出元素
  • $pullAll 从数组中有选择性的移出元素
  • $push 向数组中增添元素

以上字段只能用到数组字段上,否则会收到错误

$addToSet

$addToSet 向数组中增添元素:

1
{ $addToSet: { <field1> : <value1>, ...}}
  • 如果要插入的值不存在数组字段中
    • 新增字段会被添加到原文档中
  • 如果要插入的值已经存在数组字段中
    • $addToSet 不会再添加重复值。 使用 $addToSet插入数组和文档时,插入值中的字段顺序和已有值重复时,才被算着重复值被忽略

$addToSet 会将数组插入被更新的数组字段中,成为内嵌数组。

1
2
3
4
5
6
db.accounts.update(
{name: "karen"},
{
$addToSet: {contact: ["contact1", "contact2"]}
}
)

如果想要将多个元素直接添加到数组字段中,则需要使用 $each 操作符

1
2
3
4
5
6
db.accounts.update(
{name: "karen"},
{
$addToSet: {contact: { $each: ["contact1", "contact2"] }}
}
)

$pop

$pop 从数组字段中删除元素, 只能删除数组中的第一个(-1)和最后一个元素(1), 删除掉数组中最后一个元素后,会留下空数组

1
{ $pop: { <field>: < -1 | 1 >, ...}}

karen 的账户文档中删除最后一个联系方式:

1
2
3
4
db.accounts.update(
{name: "karen"},
{ $pop: { contact: 1 }}
)

$pull

$pull 从数组中删除符合值或者条件的元素

1
{ $pull: { <field1>: <value|condition>, ... }}

karen的联系方式中删除包含 hi字母的元素:

针对数组顶级元素的筛选无需使用 $elemMatch, 下面是错误的:

1
2
3
4
5
db.accounts.update(
{ name: "karen"},
{ $pull: { contact: { $elemMatch: {$regex: /hi/ }}}}

)

应该是:

1
2
3
4
5
db.accounts.update(
{ name: "karen"},
{ $pull: { contact: { $regex: /hi/ }}}

)

要删除数组元素是内嵌数组,可以使用 $elemMatch 对内嵌数组进行筛选:

karen的联系方式中删除包含 22222222的内嵌数组元素:

1
2
3
4
db.accounts.update(
{ name: "karen"},
{ $pull: { contact: { $elemMatch: { $eq: "22222222" }}}}
)

$pullAll

1
{ $pullAll: { <field1>: [<value1, value2>, ...], ... }}

其实

1
{ $pullAll: { <field1>: [<value1, value2>] }}

相当于

1
{ $pull: { <field1>: {$in: [<value1, value2>] }}}

如果要删除的元素是一个数组,数组的元素的值和排列顺序都必须和要被删除的数组完全一样

举个例子:

1
2
3
4
db.accounts.update(
{name: "lawrence"},
{ $pullAll: {contact: [["333333", "222222"]]}}
)

如果删除的元素是一个文档

  • $pullAll 命令只会删除字段和排列顺序都完全匹配的文档元素
  • $pull 命令会删除匹配的文档元素,模糊度更高(形成包含关系,或者能通过字段名找到)。

现有文档格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test> db.accounts.find()
[
{
_id: ObjectId("64ef4c42cb559b034e8de6a0"),
contact: [
'1',
[ '2', '3' ],
{
primaryEmail: 'xxx@gmail.com',
secondaryEmail: 'yyy@gmail.com'
}
],
name: 'alice'
}
]

pullAll 不完全匹配无法删除:

1
2
3
4
5
6
7
8
test> db.accounts.update( {name:"alice"}, {$pullAll: {contact:[{"scondaryEmail":"yyy@gmail.com"}]}})
{
acknowledged: true,
insertedId: null,
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 0
}

pull 不完全匹配可以删除:

1
2
3
4
5
6
7
8
test> db.account.update( {name:"alice"}, {$pull: {contact : {"secondaryEmail" : "yyy@gmail.com"}}})
{
acknowledged: true,
insertedId: null,
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 0
}

$push

$push 向数组中添加元素

1
{ $push: { <field1> : <value1>, ...}}

$pushaddTosSet命令相似地方:

  • 如果要插入的值不存在数组字段中

    • 新增字段会被添加到原文档中
  • 可以搭配 $each

    1
    2
    3
    4
    5
    6
    7
    8
    db.accounts.update(
    {name: "lawrence"},
    { $push: {
    newArray: {
    $each:[2, 3, 4]
    }
    }}
    )

    $pushaddTosSet增强地方:

  • 搭配 $each 实现更复杂的操作

    • 使用 $position 操作符将元素插入到数组的指定位置, $position 的值 0 表示 从数组第一个位置开始插入,-2 表示从数组最后一个元素往前走 2 个开始插入。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      db.accounts.update(
      {name: "lawrence"},
      { $push: {
      newArray: {
      $each:["pos1", "pos2"],
      $position: 0
      }
      }}
      )
    • 使用 $sort 排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      db.accounts.update(
      {name: "lawrence"},
      { $push: {
      newArray: {
      $each:["sort1"],
      $sort: 1
      }
      }}
      )
    • 如果插入的元素是内嵌文档,也可以根据内嵌文档的字段值排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      db.accounts.update(
      {name: "lawrence"},
      { $push: {
      newArray: {
      $each:[{key: "sort", value: 100}, {key: "sort1", value: 200}],
      $sort: { value: -1 } // 只排序插入的内嵌文档
      }
      }}
      )
    • 不想插入,只想对数组中的字段进行排序:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      db.accounts.update(
      {name: "lawrence"},
      { $push: {
      newArray: {
      $each:[],
      $sort: -1
      }
      }}
      )
    • 使用 $slice 截取部分数组

      插入 slice1 元素后,保留从后往前数 8 个元素:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      db.accounts.update(
      {name: "lawrence"},
      { $push: {
      newArray: {
      $each:["slice1"],
      $slice: -8
      }
      }}
      )
    • 不想插入,只想截取部分数组:

      保留数组中的前 6 个元素:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      db.accounts.update(
      {name: "lawrence"},
      { $push: {
      newArray: {
      $each:[],
      $slice: 6
      }
      }}
      )

$position, $sort, $slice 可以一起使用, 他们的执行顺序是 $position > $sort > $slice写在命令中的操作顺序并不重要,并不会影响命令的执行顺序

1
2
3
4
5
6
7
8
9
10
11
db.accounts.update(
{name: "lawrence"},
{ $push: {
newArray: {
$each:["push1", "push2"],
$position: 2,
$sort: -1,
$slice: 5
}
}}
)

$ 占位符

$ 是数组中第一个符合筛选条件的数组元素的占位符, 搭配更新操作符使用,可以对满足筛选条件的数组元素进行更新。

1
2
3
4
db.collection.update(
{ <array>: <query selector> },
{ <update operator> : { "<array>.$": value }}
)

举个例子,将 lawrencenewArray 中的 pos2 替换为 updated:

1
2
3
4
5
6
7
8
9
10
11
db.accounts.update(
{
name: "lawrence",
newArray: "pos2"
},
{
$set: {
"newArray.$": "updated"
}
}
)

$[] 占位符

$[] 指代数组中的所有元素

1
{ <update operator> : { "<array>.$[]": value }}

举个例子,将 lawrence 账户中的 contact数组中的第一个内嵌数组,全部替换为 999999:

1
2
3
4
5
6
7
8
db.accounts.update(
{name: "lawrence"},
{
$set: {
"contact.0.$[]": 999999
}
}
)

options

multi

1
{ multi: <boolean> }

默认情况下,即使筛选条件对应了多篇文档,update 命令仍然只会更新一篇文档

设置 multitrue 选项可以更新所有符合筛选条件的文档, 注意,MongoDB 只能保证单个文档操作的原子性,不能保证多个文档操作的原子性。多个文档操作时的原子性需要 MongoDB 4.0 版本引入的事务功能进行操作。

举个例子:

1
2
3
4
5
6
7
8
9
db.accounts.update(
{},
{
$set: {
currency: "USD"
},
{ multi: true}
}
)

upsert

1
{ upsert: <boolean> }

更新或者创建文档

在默认情况下,如果 update 命令中的筛选条件没有匹配任何文档,则不会进行任何操作

upsert 设置为 true, 如果 update 命令中的筛选条件没有匹配任何文档,则会创建新文档

举个例子:

1
2
3
4
5
6
7
8
9
db.accounts.update(
{name: "maggie"},
{
$set: {
balance: 700
},
{ upsert: true}
}
)

如果筛选条件中能推断出确定的字段,新创建的文档会包含筛选条件设计的字段

save

1
db.<collection>.save(<document>)

如果 document 文档中包含 _id 字段, save() 命令将会调用 db.collection.update() 命令(upsert: true), _id 值存在就更新,不存在就创建新文档。

举个例子:

1
2
3
db.accounts.save(
{ _id : "account1", name: "alice", balance: 100 }
)

删除

  • 删除集合
  • 删除特定文档

删除文档

1
db.<collection>.remove(<query>, <options>)
  • <query> 筛选条件

  • <options> 设置参数

  • 在默认情况下,remove 命令会删除所有复合筛选条件的文档

  • 如果只想删除复合筛选条件的第一篇文档,可以使用 justOne 选项

    1
    2
    3
    4
    db.accounts.remove(
    {balance: { $lt: 100} },
    {justOne: true }
    )
  • 删除集合内的所有文档

    1
    db.accounts.remove({})

举个例子, 删除 balance50 的文档:

1
2
3
db.accounts.remove(
{balance: 50 }
)

删除集合

remove 只会删除集合内所有的文档,但不会删除集合

drop 命令可以删除整个集合,包括集合中的所有文档,以及集合的索引

1
db.<collection>.drop({ writeConcern: <document> })
  • writeConcern 定义了本次集合删除操作的安全写级别

举个例子:

db.accounts.drop()

如果集合中的文档数量较多,使用 remove 命令删除所有的文档效率不高,这种情况下推荐,使用 drop 命令删除集合,然后再创建空集合并创建索引。