golang使用mongo-driver操作——增(进阶)

接着上篇的基础篇,还有一些比较不一样的数据插入。mongo对比关系型数据库的特点是,字段没有限制,那么意味着我们可以给一个文档新增字段。

上一篇我们用了InsertOneInsertMany插入文档,而现在插入字段就不是使用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"
    ],
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lsjweiyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值