• 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    第二种是如果文档不存在,则创建新的文档,而第一种是不会创建文档的:

    // 设置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值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    下面这个方法是给字段增加子字段:

    // 给字段增加子字段
    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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    下面方法是给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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面方法就是上面方法的改进,可以给满足条件的所有数组元素增加子字段:

    // 如果数组元素是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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    字段提升成文档

    这种操作被分类为新增,是因为该操作会创建新的文档,且替代掉源文档

    // 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    因为我的条件是匹配所有文档,而其中一个文档没有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]}]
    
    • 1

    为了避免上面这种错误,就可以使用下面的方法:

    // 该方法是在上一方法的基础上,增加了一个类似默认值的项,如果某个文档没有匹配到对应的字段,则会用默认值创建文档,且源文档还是会被删除
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    他的结果是:
    在这里插入图片描述
    上面方法的替代,更推荐下面这种,且这种方法演示了"_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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    当然,上面都是为了演示几个关键字的使用,正常来讲,我们肯定得加一些筛选条件去避免报错:

    // 上一方法也还有不足,因为可能没有所匹配的字段就不需要被替换呢?所以还可以通过加判断条件的方法去避免报错
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    数组相关的新增

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    看结果,这不对呀,怎么不是把元素追加进数组,而是增加了个数组,所以要么就只设置为一个字符串:bson.M{"$addToSet": bson.M{"stringlist": "addText1"}},但是这样每次只能添加一个值,多个值就很麻烦了。

    {
    ...
        "stringlist" : [
            "test1",
            "test2",
            "test3",
            [
                "addText1",
                "addText2"
            ]
        ]
    ...
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    所以需要使用each关键字:

    // 给数组新增多个元素
    updateField := bson.M{"$addToSet": bson.M{"stringlist": bson.M{"$each": []string{"addText1", "addText2"}}}}
    
    
    • 1
    • 2
    • 3

    这样的结果就对的嘛:

        "stringlist" : [
            "test1",
            "test2",
            "test3",
            "addText1",
            "addText2"
        ],
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的方法具有去重的方法,下面的方法就不会主动去重了:

    // 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    当然也可以搭配position关键字指定插入的位置:

    	// 使用push关键字给stringlist添加元素
    	// 使用$position关键字指定插入位置,还可以用负数,-1表示插入到倒数第一个元素的前面
    	// 但是这种方法必须搭配$each一起使用,哪怕值添加一个元素
    	updateField := bson.M{"$push": bson.M{"stringlist": bson.M{"$each": []string{"pushText1"}, "$position": -1}}}
    
    • 1
    • 2
    • 3
    • 4

    结果如下:

        "stringlist" : [
            "test1",
            "test2",
            "pushText1",
            "test3"
        ],
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    cmd、conhost退居二线,Win 11将设置默认终端
    golang实现打包dll文件到exe
    结构体(1)
    python --- 变量、函数、类的命名规范
    B. Paranoid String
    CSS选择器分类(儿子选择器、 序选择器、下一个兄弟选择器+)
    Day02 Spring和SpringBoot
    CPSC发布关于亚马逊含有纽扣电池或硬币电池产品的相关规则标准!UL4200A
    c++异常
    @vue/cli4--使用命令创建项目--方法/实例
  • 原文地址:https://blog.csdn.net/lsjweiyi/article/details/125393663