示例集合

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
docs := []interface{}{
	bson.D{
		{"item", "canvas"},
		{"qty", 100},
		{"size", bson.D{
			{"h", 28},
			{"w", 35.5},
			{"uom", "cm"},
		}},
		{"status", "A"},
	},
	bson.D{
		{"item", "journal"},
		{"qty", 25},
		{"size", bson.D{
			{"h", 14},
			{"w", 21},
			{"uom", "cm"},
		}},
		{"status", "A"},
	},
	bson.D{
		{"item", "mat"},
		{"qty", 85},
		{"size", bson.D{
			{"h", 27.9},
			{"w", 35.5},
			{"uom", "cm"},
		}},
		{"status", "A"},
	},
	bson.D{
		{"item", "mousepad"},
		{"qty", 25},
		{"size", bson.D{
			{"h", 19},
			{"w", 22.85},
			{"uom", "in"},
		}},
		{"status", "P"},
	},
	bson.D{
		{"item", "notebook"},
		{"qty", 50},
		{"size", bson.D{
			{"h", 8.5},
			{"w", 11},
			{"uom", "in"},
		}},
		{"status", "P"},
	},
	bson.D{
		{"item", "paper"},
		{"qty", 100},
		{"size", bson.D{
			{"h", 8.5},
			{"w", 11},
			{"uom", "in"},
		}},
		{"status", "D"},
	},
	bson.D{
		{"item", "planner"},
		{"qty", 75},
		{"size", bson.D{
			{"h", 22.85},
			{"w", 30},
			{"uom", "cm"},
		}},
		{"status", "D"},
	},
	bson.D{
		{"item", "postcard"},
		{"qty", 45},
		{"size", bson.D{
			{"h", 10},
			{"w", 15.25},
			{"uom", "cm"},
		}},
		{"status", "A"},
	},
	bson.D{
		{"item", "sketchbook"},
		{"qty", 80},
		{"size", bson.D{
			{"h", 14},
			{"w", 21},
			{"uom", "cm"},
		}},
		{"status", "A"},
	},
	bson.D{
		{"item", "sketch pad"},
		{"qty", 95},
		{"size", bson.D{
			{"h", 22.85},
			{"w", 30.5},
			{"uom", "cm"},
		}},
		{"status", "A"},
	},
}

result, err := coll.InsertMany(context.Background(), docs)

更新集合中的文档

为了更新文档,MongoDB提供了更新操作符,例如$set修改字段值。

$set如果该字段不存在,则某些更新运算符(例如)将创建该字段。

注意

从MongoDB 4.2开始,MongoDB可以接受聚合管道来指定要进行的修改而不是更新文档。

更新单个文件

以下示例在集合上使用 Collection.UpdateOne方法,更新item等于"paper"的第一个文档 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
result, err := coll.UpdateOne(
	context.Background(),
	bson.D{
		{"item", "paper"},
	},
	bson.D{
		{"$set", bson.D{
			{"size.uom", "cm"},
			{"status", "P"},
		}},
		{"$currentDate", bson.D{
			{"lastModified", true},
		}},
	},
)

更新操作:

  • 使用$set操作员更新的值 size.uom字段为"cm"和status字段为"P",
  • 使用$currentDate运算符将lastModified字段的值更新为当前日期。如果lastModified字段不存在, $currentDate将创建该字段。

更新多个文件

以下示例在集合上使用 Collection.UpdateMany方法来更新qty小于50的所有文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
result, err := coll.UpdateMany(
	context.Background(),
	bson.D{
		{"qty", bson.D{
			{"$lt", 50},
		}},
	},
	bson.D{
		{"$set", bson.D{
			{"size.uom", "cm"},
			{"status", "P"},
		}},
		{"$currentDate", bson.D{
			{"lastModified", true},
		}},
	},
)

更新操作:

  • 使用$set操作员更新的值 size.uom字段为"cm"和status字段为"P",
  • 使用$currentDate运算符将lastModified字段的值更新为当前日期。如果lastModified字段不存在, $currentDate将创建该字段。

更换文件

要替换除_id 字段以外的文档的所有内容,请将一个全新的文档作为第二个参数传递给 Collection.ReplaceOne。

替换文档时,替换文档必须仅由字段/值对组成;即不包括更新运算符表达式。

替换文档可以具有与原始文档不同的字段。在替换文档中,_id由于该_id字段是不可变的,因此可以省略该字段;但是,如果您包含该_id字段,则该字段必须与当前值具有相同的值。

下面的示例替换集合中的第一个item: "paper"的文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
result, err := coll.ReplaceOne(
	context.Background(),
	bson.D{
		{"item", "paper"},
	},
	bson.D{
		{"item", "paper"},
		{"instock", bson.A{
			bson.D{
				{"warehouse", "A"},
				{"qty", 60},
			},
			bson.D{
				{"warehouse", "B"},
				{"qty", 40},
			},
		}},
	},
)

特性

原子性

MongoDB中的所有写操作都是单个文档级别的原子操作。

_id字段

设置后,您将无法更新该_id字段的值,也无法将现有文档替换为具有不同_id字段值的替换文档。

字段顺序

除以下情况外,MongoDB在写操作后保留文档字段的顺序:

  • 该_id字段始终是文档中的第一个字段。
  • 包含renaming字段名称的更新可能会导致文档中字段的重新排序。

增补选项

当您指定选项upsert为true时:

  • 如果文档符合查询条件,请 db.collection.update()执行更新。
  • 如果没有文件的查询条件匹配, db.collection.update()插入一个单一的文件。

如果在分片集合上指定,则必须在中包含完整的分片密钥。

警告:为避免多次插入同一文档,请仅在字段被唯一索引时使用。

给定一个名为集合people在没有文档有name保存的价值领域Andy,考虑当多个客户端在同一时间发出以下db.collection.update()与upsert: true:

1
2
3
4
5
6
7
8
9
db.people.update(
   { name: "Andy" },   // Query parameter
   {                   // Update document
      name: "Andy",
      rating: 1,
      score: 1
   },
   { upsert: true }    // Options
)

如果所有db.collection.update()操作query在任何客户端成功插入数据之前完成了该部分,并且该name字段上没有唯一索引,则每个更新操作都可能导致插入。

为防止MongoDB多次插入同一文档,请在字段上创建唯一索引name。使用唯一索引,如果多个应用程序使用发出相同的更新,则恰好一个将成功插入一个新文档。

其余操作将是:

  • 更新新插入的文档,或者
  • 当他们尝试插入重复项时失败。如果操作由于重复的索引键错误而失败,则应用程序可以重试该操作,该操作将作为更新操作成功。

写确认

对于写入问题,您可以指定MongoDB向写入操作请求的确认级别。

聚合管道更新

从MongoDB 4.2开始,您可以将聚合管道用于更新操作。通过更新操作,聚合管道可以包括以下阶段:

  • $addFields
  • $set
  • $project
  • $unset
  • $replaceRoot
  • $replaceWith

使用聚合管道可以实现更具表达力的更新语句,例如根据当前字段值表达条件更新,或使用另一个字段的值更新一个字段。

示例1

创建一个示例students集合(如果该集合当前不存在,则插入操作将创建该集合):

1
2
3
4
5
db.students.insertMany([
   { _id: 1, test1: 95, test2: 92, test3: 90, modified: new Date("01/05/2020") },
   { _id: 2, test1: 98, test2: 100, test3: 102, modified: new Date("01/05/2020") },
   { _id: 3, test1: 95, test2: 110, modified: new Date("01/04/2020") }
])

以下db.collection.updateOne()操作使用聚合管道通过以下方式更新文档:_id: 3

1
db.students.updateOne( { _id: 3 }, [ { $set: { "test3": 98, modified: "$$NOW"} } ] )

具体来说,管道包含一个$set阶段,该阶段将添加test3:98到文档中(并将其值设置为),并将modified字段设置为当前日期时间。对于当前日期时间,该操作将聚合变量NOW用于(访问变量,在变量前加前缀$$ 并用引号引起来)。

示例2

创建一个示例students2集合(如果该集合当前不存在,则插入操作将创建该集合):

1
2
3
4
db.students2.insertMany([
   { "_id" : 1, quiz1: 8, test2: 100, quiz2: 9, modified: new Date("01/05/2020") },
   { "_id" : 2, quiz2: 5, test1: 80, test2: 89, modified: new Date("01/05/2020") },
])

以下 db.collection.updateMany()操作使用聚合管道来标准化文档的字段(即,集合中的文档应具有相同的字段)并更新该 modified字段:

1
2
3
4
5
6
7
8
db.students2.updateMany( {},
  [
    { $replaceRoot: { newRoot:
       { $mergeObjects: [ { quiz1: 0, quiz2: 0, test1: 0, test2: 0 }, "$$ROOT" ] }
    } },
    { $set: { modified: "$$NOW"}  }
  ]
)

具体来说,管道包括:

  • 一个$replaceRoot与$mergeObjects表达为设置默认值quiz1,quiz2,test1和test2字段。聚合变量ROOT是指正在修改的当前文档(以访问该变量,使用前缀 $$和引号引起来)。当前文档字段将覆盖默认值。
  • 一个$set阶段到更新modified领域的当前日期时间。对于当前日期时间,该操作将聚合变量NOW用于(访问变量,在变量前加前缀$$并用引号引起来)。

示例3

创建一个示例students3集合(如果该集合当前不存在,则插入操作将创建该集合):

1
2
3
4
5
db.students3.insert([
   { "_id" : 1, "tests" : [ 95, 92, 90 ], "modified" : ISODate("2019-01-01T00:00:00Z") },
   { "_id" : 2, "tests" : [ 94, 88, 90 ], "modified" : ISODate("2019-01-01T00:00:00Z") },
   { "_id" : 3, "tests" : [ 70, 75, 82 ], "modified" : ISODate("2019-01-01T00:00:00Z") }
]);

以下db.collection.updateMany()操作使用聚合管道以计算的平均成绩和字母成绩更新文档。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
db.students3.updateMany(
   { },
   [
     { $set: { average : { $trunc: [ { $avg: "$tests" }, 0 ] }, modified: "$$NOW" } },
     { $set: { grade: { $switch: {
                           branches: [
                               { case: { $gte: [ "$average", 90 ] }, then: "A" },
                               { case: { $gte: [ "$average", 80 ] }, then: "B" },
                               { case: { $gte: [ "$average", 70 ] }, then: "C" },
                               { case: { $gte: [ "$average", 60 ] }, then: "D" }
                           ],
                           default: "F"
     } } } }
   ]
)

具体来说,管道包括:

  • $set阶段,用于计算tests数组元素的截断平均值并将modified 字段更新为当前日期时间。要计算平均值,阶段使用$avg和$trunc 表达式。对于当前日期时间,该操作将聚合变量NOW用于(访问变量,使用前缀$$并用引号引起来)
  • 基于使用表达式$set添加grade字段

示例4

创建一个示例students4集合(如果该集合当前不存在,则插入操作将创建该集合):

1
2
3
4
5
db.students4.insertMany([
  { "_id" : 1, "quizzes" : [ 4, 6, 7 ] },
  { "_id" : 2, "quizzes" : [ 5 ] },
  { "_id" : 3, "quizzes" : [ 10, 10, 10 ] }
])

以下db.collection.updateOne()操作使用聚合管道通过以下方式向文档_id: 2添加测验分数:

1
2
3
db.students4.updateOne( { _id: 2 },
  [ { $set: { quizzes: { $concatArrays: [ "$quizzes", [ 8, 6 ]  ] } } } ]
)

示例5

创建一个temperatures包含摄氏温度的示例集合(如果该集合当前不存在,则插入操作将创建该集合):

1
2
3
4
5
db.temperatures.insertMany([
  { "_id" : 1, "date" : ISODate("2019-06-23"), "tempsC" : [ 4, 12, 17 ] },
  { "_id" : 2, "date" : ISODate("2019-07-07"), "tempsC" : [ 14, 24, 11 ] },
  { "_id" : 3, "date" : ISODate("2019-10-30"), "tempsC" : [ 18, 6, 8 ] }
])

以下db.collection.updateMany()操作使用聚合管道以华氏度的相应温度更新文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
db.temperatures.updateMany( { },
  [
    { $addFields: { "tempsF": {
          $map: {
             input: "$tempsC",
             as: "celsius",
             in: { $add: [ { $multiply: ["$$celsius", 9/5 ] }, 32 ] }
          }
    } } }
  ]
)

具体来说,管道包括一个$addFields 阶段,以添加一个新的数组字段tempsF,该字段包含华氏温度。要将tempsC数组中的每个摄氏温度转换为华氏温度,该阶段使用 $map带有$add和的 $multiply表达式。