0

I have a collection with documents with the following structure:

{
    "_id": ObjectId("..."),
    "rides": [
        {
            status_history: ["status1", "status2", "status3"]
        },
        {
            status_history: ["status4", "status5"]
        }
        ...
    ]
}

What I want to do is append a new status to the status_history array of the the last ride in the rides array, achieving something like:

{
    "_id": ObjectId("..."),
    "rides": [
        {
            status_history: ["status1", "status2", "status3"]
        },
        {
            status_history: ["status4", "status5", "NEW_STATUS_HERE"]
        }
        ...
    ]
}

But I can't quite figure out how to do that. I'm using the mongo-driver package to connect to the db.

Here's the code I managed to come up with:

filter := bson.M{"_id": objectId}

arrayFilters := options.ArrayFilters{
    Filters: bson.A{
        bson.M{"s": bson.M{"rides": bson.A{"$slice", -1}}},
    },
}
upsert := true
opts := options.UpdateOptions{
    ArrayFilters: &arrayFilters,
    Upsert:       &upsert,
}
update := bson.M{
    "$push": bson.M{
        "rides.$[s].status_history": statusEntry,
    },
}

result, err := col.UpdateOne(ctx, filter, update, &opts)

fmt.Println(fmt.Sprintf(
    "Matched %v, Modified %v, Upserted %v",
    result.MatchedCount,
    result.ModifiedCount,
    result.UpsertedCount,
))

The output of that Println is Matched 1, Modified 0, Upserted 0. Indeed, inspecting the item in the collection shows that it is unchanged.

What exactly am I getting wrong here?

2
  • Which status_history array do you want to update (or add a new status to) - supposing if there are 10 of them? Commented Nov 4, 2021 at 4:40
  • @prasad_ Each object inside the rides array will always have exactly one status_history array. So what I want is to append a new status to the one status_history array inside the last object in the rides array. Commented Nov 4, 2021 at 12:21

2 Answers 2

1

This Update With Aggregation Pipeline (feature requires MongoDB v4.2 or higher) will update the last element (nested document) of the rides array, with a new status value added to the status_history array field. The update runs in mongo shell.

let NEW_VALUE = "new_status_999";

db.test.updateOne(
{ _id: 1 },
[
{ 
    $set: {
        rides: { 
            $concatArrays: [
                { $slice: [ "$rides", { $subtract: [ { $size: "$rides" }, 1 ] } ] },
                [ {
                    $let: {
                        vars: { lastEle: { $arrayElemAt: [ { $slice: [ "$rides", -1 ] }, 0 ] } }, 
                        in: {
                            $mergeObjects: [ 
                                "$$lastEle", 
                                { status_history: { $concatArrays: [ "$$lastEle.status_history", [ NEW_VALUE ] ] } } 
                            ]
                        }
                    }
                }]
            ]
        }
    }
}
])

The updated document:

{
        "_id" : 1,
        "rides" : [
                {
                        "status_history" : [
                                "status1",
                                "status2",
                                "status3"
                        ]
                },
                {
                        "status_history" : [
                                "status4",
                                "status5",
                                "new_status_999"    // <--- the update result
                        ]
                }
        ]
}
Sign up to request clarification or add additional context in comments.

5 Comments

Ahhhh that works like a charm! Thank you so much man, I really appreciate it!
i think for empty rides doesnt work check this , maybe you can use @prasad_ way with small change
One warning for future readers: this answer works in a slightly weird way if the rides array is empty: in that case, it inserts a new object into it: but with null instead of the newValue in the history: {"status_history": null} (see here). That's not a case that I care about, so it's good enough for me. But if that's something that you have to handle, be sure to add some extra checks for that case, or go with @Takis_ answer which does handle that case!
Thats correct, that the query assumes that there is at least one element in the array.
In case rides is an empty array (rides: []), one can add this stage before the existing one: { $set: { rides: { $cond: [ { $eq: [ { $size: "$rides" }, 0 ] }, [ { status_history: [ ] } ], "$rides" ] } } },. Any other conditions can be coded with a similar approach.
1

Query

  • pipeline update requires MongoDB >= 4.2
  • find the size of the array
  • if size>1 slice to get the prv else prv=[]
  • concat arrays prv with {"status" "array_with_new_member"}

*i dont use go, for Java the update method accepts a pipeline also, so it works simple, in the worst case you can use it as update command.

PlayMongo

update({},
[{"$set":{"size":{"$size":"$rides"}}},
 {"$set":
  {"rides":
   {"$concatArrays":
    [{"$cond":
      [{"$gt":["$size", 1]},
       {"$slice":["$rides", 0, {"$subtract":["$size", 1]}]}, []]},
     [{"status_history":
       {"$concatArrays":
        [{"$cond":
          [{"$gt":["$size", 0]},
           {"$arrayElemAt":
            ["$rides.status_history", {"$subtract":["$size", 1]}]},
           []]}, ["new-status"]]}}]]}}},
  {"$unset":["size"]}])

7 Comments

Hey, thanks for the reply! I tried converting that to the Go language but didn't really manage to. And unfortunately, as far as I can tell, the mongo-driver package does not allow for just writing the query directly in a string and sending it (which seems like a weird omission). I appreciate the help though, will keep trying to use your suggestion some more.
you have to find out the way to write pipeline updates in go languange, not for this query only, they are very important and useful in general. If you cant find an update method with pipeline(java has one), you can always use an update command see this and this , for JSON strings java has a function called parse i guess go has something similar.
I managed to get it to work! What I had not realized was that I needed to change the col.UpdateOne call to col.Aggregate. I did find an issue with the solution though: it fails if the rides array contains exactly 1 element: PlanExecutor error during aggregation :: caused by :: Third argument to $slice must be positive: 0
you mean your query or the one i sended? its good if your query worked to do it in the way you understand better, the query i sended work for all cases empty array, 1 element, or more. aggregate cant do update, unless you use $merge. Find how to do pipeline updates in go, sometime.
The one you sent, yeah. See here for example of same code you sent, but with only one element in the rides array. I am looking into pipeline updates, thanks!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.