接着上篇的基础篇,还有一些比较不一样的数据插入。mongo对比关系型数据库的特点是,字段没有限制,那么意味着我们可以给一个文档新增字段。
上一篇我们用了InsertOne
和InsertMany
插入文档,而现在插入字段就不是使用insert,而是使用update,因为我们没有新建document。但我认为这也属于增加操作,所以写在这里。
给已有文档插入新字段
第一种先查询是否存在符合条件的文档,如果有则插入字段,如果该字段已存在,则更新值
// 给document新增字段
func AddField(mongo *mongo.Database, ctx context.Context) {
// 转换成objectid的类型,否则查询不到的
objectid, err := primitive.ObjectIDFromHex("62b07d4ac1e231479c955df6")
if err != nil {
fmt.Println(err)
}
filter := bson.M{"objectid": objectid} // 查询的条件,相当于mysql的where后面的条件
// 据官方文档描述,$set是$addFields在4.2版本的别名,但实测addFields无法在下列语句中使用,说明他们还是不一样的。目前用起来,set挺好。具体看文档:https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#mongodb-pipeline-pipe.-addFields
updateField := bson.M{"$set": bson.M{"intArray": []int32{1, 2, 3, 4, 5, 6}}} // set字段表示新增字段,可以有多个字段,如果该字段已存在,则会覆盖原来的值
result, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
第二种是如果文档不存在,则创建新的文档,而第一种是不会创建文档的:
// 设置Upsert,就会在没有查询到对应文档时,新增一个文档,且查询字段会被作为其中一个属性插入
func AddFieldWithUpsert(mongo *mongo.Database, ctx context.Context) {
// 转换成objectid的类型,否则查询不到的
objectid, err := primitive.ObjectIDFromHex("62b07d4ac1e231479c955df1")
if err != nil {
fmt.Println(err)
}
filter := bson.M{"objectid": objectid} // 查询的条件,相当于mysql的where后面的条件
// 据官方文档描述,$set是$addFields在4.2版本的别名,但实测addFields无法在下列语句中使用,说明他们还是不一样的。目前用起来,set挺好。具体看文档:https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#mongodb-pipeline-pipe.-addFields
updateField := bson.M{"$set": bson.M{"intArray": []int32{1, 2, 3, 4, 5, 6}}} // set字段表示新增字段,可以有多个字段,如果该字段已存在,则会覆盖原来的值
upsert := true
updateOption := options.UpdateOptions{Upsert: &upsert}
result, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField, &updateOption)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
fmt.Println("插入文档的id:", result.UpsertedID) // 返回新增文档的_id值
}
下面这个方法是给字段增加子字段:
// 给字段增加子字段
func AddChildField(mongo *mongo.Database, ctx context.Context) {
// 这里仅支持给object类型增加子字段,直接使用.即可
updateField := bson.M{"$set": bson.M{"object.child": bson.A{bson.M{"text1": "child1"}, bson.M{"text2": "child2"}, bson.M{"text3": "child3"}}}} // set字段表示新增字段,可以有多个字段,如果该字段已存在,则会覆盖原来的值
_, err := mongo.Collection("test").UpdateOne(ctx, bson.M{}, updateField)
if err != nil {
fmt.Println(err)
}
}
下面方法是给object类型的数组元素增加子字段,但每次只能给一个元素增加:
// 如果数组元素是object,那么可以使用下面方法给其中一个元素新增一个字段,如果满足条件的元素有好多个,只会给第一个元素新增字段
func AddArrayChildField(mongo *mongo.Database, ctx context.Context) {
// 这种方法必须将需要新增字段的数组包含在查询条件中,否则会报错,例如下面的object.child,child就是需要新增字段的数组
filter := bson.M{"object.child": bson.M{"$type": 3}}
// $[elem] 与arrayFilter配合,起到下标匹配的效果,elem就是下标的代指
updateField := bson.M{"$set": bson.M{"object.child.$.text4": "child4"}} // set字段表示新增字段,可以有多个字段,如果该字段已存在,则会覆盖原来的值
_, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField)
if err != nil {
fmt.Println(err)
}
}
下面方法就是上面方法的改进,可以给满足条件的所有数组元素增加子字段:
// 如果数组元素是object,那么可以使用下面方法给每个元素增加一个新的字段text4
func AddArrayChildAllField(mongo *mongo.Database, ctx context.Context) {
filter := bson.M{} // 查询的条件,相当于mysql的where后面的条件
// $[elem] 与arrayFilter配合,起到下标匹配的效果,elem就是下标的代指。具体见:https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered/
updateField := bson.M{"$set": bson.M{"object.child.$[elem].text4": "child4"}} // set字段表示新增字段,可以有多个字段,如果该字段已存在,则会覆盖原来的值
arrayFilter := options.ArrayFilters{Filters: bson.A{bson.M{"elem.text4": bson.M{"$exists": false}}}}
updateOption := options.UpdateOptions{ArrayFilters: &arrayFilter}
_, err := mongo.Collection("test").UpdateOne(ctx, filter, updateField, &updateOption)
if err != nil {
fmt.Println(err)
}
}
字段提升成文档
这种操作被分类为新增,是因为该操作会创建新的文档,且替代掉源文档
// replaceWith能够将指定的字段提升为根文档,也替换掉原来的根文档,该操作也创建了一个新的文档。
func AddFieldWithReplaceWith(mongo *mongo.Database, ctx context.Context) {
// replaceWith是replaceRoot的别名,与上面的set来源相同。具体看文档:https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#mongodb-pipeline-pipe.-replaceWith
// replaceWith关键字是使文档中的某个object字段,替换掉该文档,使其成为一个文档,类型必须是object
// 且所有匹配了查询条件的文档,必须包含该字段,否则也会报错。
updateField := bson.A{bson.M{"$replaceWith": "$object"}}
result, err := mongo.Collection("test").UpdateMany(ctx, bson.M{}, updateField)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
因为我的条件是匹配所有文档,而其中一个文档没有object字段,所以报错了:
write exception: write errors: ['replacement document' must evaluate to an object, but resulting value was: MISSING. Type of resulting value: 'missing'. Input document: {_id: 62b45af32b1f66a58503bf26, objectid: 62b07d4ac1e231479c955df1, intArray: [1, 2, 3, 4, 5, 6]}]
为了避免上面这种错误,就可以使用下面的方法:
// 该方法是在上一方法的基础上,增加了一个类似默认值的项,如果某个文档没有匹配到对应的字段,则会用默认值创建文档,且源文档还是会被删除
func AddFieldWithMergeObjects(mongo *mongo.Database, ctx context.Context) {
//mergeObjects实际作用是合并多个文档,且放在后面的文档会覆盖前面的文档,
// 所以,当我们需要replaceWith的文档没有object字段时,就会因为合并了前面的文档,而达到类似于默认值的效果
// 从而避免了文档不包括该字段会报错的问题
// 注意"$object"要放在后面
updateField := bson.A{bson.M{"$replaceWith": bson.M{"$mergeObjects": bson.A{bson.M{"text": "默认值"}, "$object"}}}}
result, err := mongo.Collection("test").UpdateMany(ctx, bson.M{}, updateField)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
他的结果是:
上面方法的替代,更推荐下面这种,且这种方法演示了"_id": "$_id"
使用旧文档的id来作为新文档的id的方法:
// 上一种方法还有个替代方案
func AddFieldWithIfNull(mongo *mongo.Database, ctx context.Context) {
// ifNull如字面意思,判断是否存在object,如果不存在,则返回谋面的内容作为结果,和上面的方法一样的效果
updateOption := bson.A{bson.M{"$replaceWith": bson.M{"$ifNull": bson.A{"$object", bson.M{"_id": "$_id", "missingObject": true}}}}}
result, err := mongo.Collection("test").UpdateMany(ctx, bson.M{}, updateOption)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
当然,上面都是为了演示几个关键字的使用,正常来讲,我们肯定得加一些筛选条件去避免报错:
// 上一方法也还有不足,因为可能没有所匹配的字段就不需要被替换呢?所以还可以通过加判断条件的方法去避免报错
func AddFieldWithFind(mongo *mongo.Database, ctx context.Context) {
// 给UpdateMany加一些筛选条件,避免出现不存在object字段的问题
findField := bson.M{"object": bson.M{"$exists": true, "$type": 3}}
updateField := bson.A{bson.M{"$replaceWith": "$object"}}
result, err := mongo.Collection("test").UpdateMany(ctx, findField, updateField)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
数组相关的新增
addToSet关键字能把内容追加到数组,但是不能追加数组中已存在的元素,但原来就重复的元素它不管,具体见:https://www.mongodb.com/docs/manual/reference/operator/update/addToSet/
// 给数组新增元素
func AddArrayElement(mongo *mongo.Database, ctx context.Context) {
// 给UpdateMany加一些筛选条件,避免出现不存在object字段的问题
findField := bson.M{"stringlist": bson.M{"$exists": true, "$type": 4}}
// 使用addToSet关键字给stringlist添加一个元素
updateField := bson.M{"$addToSet": bson.M{"stringlist": []string{"addText1", "addText2"}}}
result, err := mongo.Collection("test").UpdateOne(ctx, findField, updateField)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
看结果,这不对呀,怎么不是把元素追加进数组,而是增加了个数组,所以要么就只设置为一个字符串:bson.M{"$addToSet": bson.M{"stringlist": "addText1"}}
,但是这样每次只能添加一个值,多个值就很麻烦了。
{
...
"stringlist" : [
"test1",
"test2",
"test3",
[
"addText1",
"addText2"
]
]
...
}
所以需要使用each关键字:
// 给数组新增多个元素
updateField := bson.M{"$addToSet": bson.M{"stringlist": bson.M{"$each": []string{"addText1", "addText2"}}}}
这样的结果就对的嘛:
"stringlist" : [
"test1",
"test2",
"test3",
"addText1",
"addText2"
],
上面的方法具有去重的方法,下面的方法就不会主动去重了:
// push方法与addToSet不同的是不会检查是否重复,push保证元素一定添加到末尾
func AddArrayElementWithPush(mongo *mongo.Database, ctx context.Context) {
// 给UpdateMany加一些筛选条件,避免出现不存在object字段的问题
findField := bson.M{"stringlist": bson.M{"$exists": true, "$type": 4}}
// 使用push关键字给stringlist添加元素
updateField := bson.M{"$push": bson.M{"stringlist": "pushText1"}} // 也可以像上面那样使用$each添加多个元素
result, err := mongo.Collection("test").UpdateOne(ctx, findField, updateField)
if err != nil {
fmt.Println(err)
}
fmt.Println("匹配文档的数量:", result.MatchedCount)
fmt.Println("修改文档的数量:", result.MatchedCount)
}
当然也可以搭配position关键字指定插入的位置:
// 使用push关键字给stringlist添加元素
// 使用$position关键字指定插入位置,还可以用负数,-1表示插入到倒数第一个元素的前面
// 但是这种方法必须搭配$each一起使用,哪怕值添加一个元素
updateField := bson.M{"$push": bson.M{"stringlist": bson.M{"$each": []string{"pushText1"}, "$position": -1}}}
结果如下:
"stringlist" : [
"test1",
"test2",
"pushText1",
"test3"
],