MongoDB更新文档(二)之数组运算符

TrumanWong
7/13/2024

MongoDB 中有一大类更新运算符用于操作数组。数组是常用且功能强大的数据结构:它们不仅是可以通过索引进行引用的列表,而且可以作为集合来使用。

添加元素

创建 students 集合:

> db.students.insertOne( { _id: 1, scores: [ 44, 78, 38, 80 ] } )
{ acknowledged: true, insertedId: 1 }

如果数组已存在,$push 就会将元素添加到数组末尾;如果数组不存在,则会创建一个新的数组。

> db.students.updateOne(
...    { _id: 1 },
...    { $push: { scores: 89 } }
... )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.students.find()
[ { _id: 1, scores: [ 44, 78, 38, 80, 89 ] } ]

您可以使用 $push 操作符与以下修饰符一起使用:

修饰符说明
$each向数组字段追加多个值。
$slice限制数组元素的数量。需要使用 $each 修改器。
$sort对数组元素进行排序。需要使用 $each 修饰符。
$position指定数组中插入新元素的位置。需要使用 $each 修饰符。如果没有 $position 修饰符,$push 会将元素追加到数组的末尾。

在多个文档中向数组追加值

将以下文档添加到 students 集合中:

> db.students.insertMany( [
...    { _id: 2, scores: [ 45, 78, 38, 80, 89 ] } ,
...    { _id: 3, scores: [ 46, 78, 38, 80, 89 ] } ,
...    { _id: 4, scores: [ 47, 78, 38, 80, 89 ] }
... ] )
{ acknowledged: true, insertedIds: { '0': 2, '1': 3, '2': 4 } }

以下 $push操作会向每个文档的 scores 数组追加 95

> db.students.updateMany(
...    { },
...    { $push: { scores: 95 } }
... )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 4,
  modifiedCount: 4,
  upsertedCount: 0
}
> db.students.find()
[
  { _id: 1, scores: [ 44, 78, 38, 80, 89, 95 ] },
  { _id: 2, scores: [ 45, 78, 38, 80, 89, 95 ] },
  { _id: 3, scores: [ 46, 78, 38, 80, 89, 95 ] },
  { _id: 4, scores: [ 47, 78, 38, 80, 89, 95 ] }
]

向数组追加多个值

使用 $push$each 修饰符将多个值附加到数组字段。

以下示例将 [ 90, 92, 85 ] 的每个元素附加到文档的 scores 数组,其中 name 字段等于 joe::

> db.students.updateOne( { _id: 1 }, { $push: { scores: { $each: [90, 92, 85] } } } )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.students.find({_id: 1})
[
  {
    _id: 1,
    scores: [
      44, 78, 38, 80, 89,
      95, 90, 92, 85
    ]
  }
]

使用带有多个修饰符的 $push 操作符

如果只允许数组增长到某个长度,则可以使用 $slice 修饰符配合 $push 来防止数组的增长超过某个大小,从而有效地生成top N列表:

> db.students.insertOne(
...    {
...       "_id" : 5,
...       "quizzes" : [
...          { "wk": 1, "score" : 10 },
...          { "wk": 2, "score" : 8 },
...          { "wk": 3, "score" : 5 },
...          { "wk": 4, "score" : 6 }
...       ]
...    }
... )
{ acknowledged: true, insertedId: 5 }
> db.students.updateOne(
...    { _id: 5 },
...    {
...      $push: {
...        quizzes: {
...           $each: [ { wk: 5, score: 8 }, { wk: 6, score: 7 }, { wk: 7, score: 6 } ],
...           $sort: { score: -1 },
...           $slice: 3
...        }
...      }
...    }
... )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}

操作后,数组中只有三个得分最高的:

> db.students.findOne({_id: 5})
{
  _id: 5,
  quizzes: [ { wk: 1, score: 10 }, { wk: 2, score: 8 }, { wk: 5, score: 8 } ]
}
注意,不能只将 $slice$sort$push 配合使用,必须包含 $each

$addToSet

$addToSet 操作符会将值添加到数组中,除非该值已经存在,在这种情况下,$addToSet 对该数组不执行任何操作。

删除元素

$pop

$pop 操作符删除数组的第一个元素或最后一个元素。为 $pop 传入 -1 的值,可以删除数组中的第一个元素,传入 1 的值则可以删除数组中的最后一个元素。

> > db.students.insertOne( { _id: 1, scores: [ 8, 9, 10 ] } )
{ acknowledged: true, insertedId: 1 }
true

删除数组第一项:

> db.students.updateOne( { _id: 1 }, { $pop: { scores: -1 } } )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.students.findOne({_id: 1})
{ _id: 1, scores: [ 9, 10 ] }

删除数组最后一项

> db.students.updateOne( { _id: 1 }, { $pop: { scores: 1 } })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.students.findOne({_id: 1})
{ _id: 1, scores: [ 9 ] }

$pull

$pull 操作符会从现有数组中删除符合指定条件的所有匹配项:

> db.profiles.insertOne( { _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] } )
{ acknowledged: true, insertedId: 1 }
> db.profiles.updateOne( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}

更新后,文档只有小于6的值:

> db.profiles.findOne({_id: 1})
{ _id: 1, votes: [ 3, 5 ] }

基于位置的数组更改

定位运算符$标识数组中要更新的元素,而无需显式指定该元素在数组中的位置。

创建集合:

> db.students.insertMany( [
...    { "_id" : 1, "grades" : [ 85, 80, 80 ] },
...    { "_id" : 2, "grades" : [ 88, 90, 92 ] },
...    { "_id" : 3, "grades" : [ 85, 100, 90 ] }
... ] )
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }

更新数组中的值

定位运算符只会更新第一个匹配到的元素

> db.students.updateOne(
...    { _id: 1, grades: 80 },
...    { $set: { "grades.$" : 82 } }
... )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.students.find({"_id": 1})
[ { _id: 1, grades: [ 85, 82, 80 ] } ]

更新数组中的文档

$ 运算符可以更新包含嵌入文档的数组。使用 $ 运算符,可通过 $ 运算符上的点符号访问嵌入文档中的字段:

db.collection.updateOne(
   { <query selector> },
   { <update operator>: { "array.$.field" : value } }
)

使用 $ 运算符来更新与“grade 等于 85 条件”相匹配的第一个数组元素的 std 字段:

> db.students.insertOne({
...   _id: 4,
...   grades: [
...      { grade: 80, mean: 75, std: 8 },
...      { grade: 85, mean: 90, std: 5 },
...      { grade: 85, mean: 85, std: 8 }
...   ]
... })
{ acknowledged: true, insertedId: 4 }
> db.students.updateOne(
...    { _id: 4, "grades.grade": 85 },
...    { $set: { "grades.$.std" : 6 } }
... )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.students.findOne({_id: 4})
{
  _id: 4,
  grades: [
    { grade: 80, mean: 75, std: 8 },
    { grade: 85, mean: 90, std: 6 },
    { grade: 85, mean: 85, std: 8 }
  ]
}

使用数组过滤器进行更新

过滤位置操作符 $[<identifier>] 用于识别匹配更新操作 arrayFilters 条件的数组元素。

arrayFilters 选项一起使用时,$[<identifier>] 操作符的形式如下:

{ <update operator>: { "<array>.$[<identifier>]" : value } },
{ arrayFilters: [ { <identifier>: <condition> } ] }

创建集合:

> db.students.insertMany( [
   { "_id" : 1, "grades" : [ 95, 92, 90 ] },
   { "_id" : 2, "grades" : [ 98, 100, 102 ] },
   { "_id" : 3, "grades" : [ 95, 110, 100 ] }
] )

更新数组中 grades 大于或等于 100 的所有元素:

> db.students.updateMany(
...    { },
...    { $set: { "grades.$[element]" : 100 } },
...    { arrayFilters: [ { "element": { $gte: 100 } } ] }
... )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 3,
  modifiedCount: 2,
  upsertedCount: 0
}
> db.students.find()
[
  { _id: 1, grades: [ 95, 92, 90 ] },
  { _id: 2, grades: [ 98, 100, 100 ] },
  { _id: 3, grades: [ 95, 100, 100 ] }
]