Search code examples
mongodbgomongo-go

How would I update multiple records based on different key in Mongo in one query?


If I have something similar to the following...

collection.InsertMany(context.TODO(), []interface{}{
   bson.M{ "_id" : 1, "member" : "abc123", "status" : "P" },
   bson.M{ "_id" : 2, "member" : "xyz123", "status" : "A" },
   bson.M{ "_id" : 3, "member" : "lmn123", "status" : "P" },
   bson.M{ "_id" : 4, "member" : "pqr123", "status" : "D" },
   bson.M{ "_id" : 5, "member" : "ijk123", "status" : "P" },
   bson.M{ "_id" : 6, "member" : "cde123", "status" : "A" },
} )

Is is possible to apply the following update in one InsertMany query?

[{"_id" : "1", "status" : "P0-A0"},
 {"_id" : "2", "status" : "P0-A1"},
 {"_id" : "3", "status" : "P0-A2"},
 {"_id" : "4", "status" : "P0-A3"},
 {"_id" : "5", "status" : "P0-A4"},
 {"_id" : "6", "status" : "P0-A5"}]

If so, how would that be done with golang?

Specifically, using collection.UpdateMany(context.TODO(), filter, update), what would I have for my filter and update?

Thanks for your help.


Solution

  • You can't do it with a single Collection.UpdateMany() call, because you can't apply different update documents on different matched documents. You would have to call Collection.UpdateMany() many times, once for each different update document.

    If you want to do it efficiently, with a single call, you may use Collection.BulkWrite(). You have to prepare a different mongo.WriteModel for each document update.

    Here's how it could look like:

    wm := []mongo.WriteModel{
        mongo.NewUpdateOneModel().SetFilter(bson.M{"_id": "1"}).SetUpdate(bson.M{"$set": bson.M{"status": "P0-A0"}}),
        mongo.NewUpdateOneModel().SetFilter(bson.M{"_id": "2"}).SetUpdate(bson.M{"$set": bson.M{"status": "P0-A1"}}),
        mongo.NewUpdateOneModel().SetFilter(bson.M{"_id": "3"}).SetUpdate(bson.M{"$set": bson.M{"status": "P0-A2"}}),
        mongo.NewUpdateOneModel().SetFilter(bson.M{"_id": "4"}).SetUpdate(bson.M{"$set": bson.M{"status": "P0-A3"}}),
        mongo.NewUpdateOneModel().SetFilter(bson.M{"_id": "5"}).SetUpdate(bson.M{"$set": bson.M{"status": "P0-A4"}}),
        mongo.NewUpdateOneModel().SetFilter(bson.M{"_id": "6"}).SetUpdate(bson.M{"$set": bson.M{"status": "P0-A5"}}),
    }
    

    There's too much "repetition" in the above slice literal, you can capture them using a helper function:

    create := func(id, newStatus string) *mongo.UpdateOneModel {
        return mongo.NewUpdateOneModel().
            SetFilter(bson.M{"_id": id}).
            SetUpdate(bson.M{"$set": bson.M{"status": newStatus}})
    }
    
    wm := []mongo.WriteModel{
        create("1", "P0-A0"),
        create("2", "P0-A1"),
        create("3", "P0-A2"),
        create("4", "P0-A3"),
        create("5", "P0-A4"),
        create("6", "P0-A5"),
    }
    

    Also if there's logic in the update which you can easily define, use a loop instead of listing all the elements:

    var wm []mongo.WriteModel
    for i := 1; i <= 6; i++ {
        newStatus := fmt.Sprintf("P0-A%d", i-1)
        wm = append(wm, mongo.NewUpdateOneModel().
            SetFilter(bson.M{"_id": strconv.Itoa(i)}).
            SetUpdate(bson.M{"$set": bson.M{"status": newStatus}}),
        )
    }
    

    And you may execute all the updates with one call like this:

    res, err := coll.BulkWrite(ctx, wm)
    

    See related: MongoDB update array of documents and replace by an array of replacement documents