I have a collection in the following format:
[
{
"postId": ObjectId("62dffd0acb17483cf015375f"),
"userId": ObjectId("62dff9584f5b702d61c81c3c"),
"state": [
{
"id": ObjectId("62dffc49cb17483cf0153220"),
"notes": "these are my custom notes!",
"lvl": 3,
},
{
"id": ObjectId("62dffc49cb17483cf0153221"),
"notes": "hello again",
"lvl": 0,
},
]
},
]
My goal is to be able to update and add an element in this array in the following situation:
- If the ID of the new element is not in the
state
array, push the new element in the array - If the ID of the new element is in the
state
array and itslvl
field is 0, update that element with the new information - If the ID of the new element exists in the array, and its
lvl
field is not 0, then nothing should happen. I will throw an error by seeing that no documents were matched.
Basically, to accomplish this I was thinking about using findOneAndUpdate
with upsert, but I am not sure how to tell the query to update the state if lvl
is 0 or don’t do anything if it is bigger than 0 when the match is found.
For solving (1) this is what I was able to come up with:
db.collection.findOneAndUpdate(
{
"postId": ObjectId("62dffd0acb17483cf015375f"),
"userId": ObjectId("62dff9584f5b702d61c81c3c"),
"state.id": {
"$ne": ObjectId("62dffc49cb17483cf0153222"),
},
},
{
"$push": {"state": {"id": ObjectId("62dffc49cb17483cf0153222"), "lvl": 1}}
},
{
"new": true,
"upsert": true,
}
)
What is the correct way to approach this issue? Should I just split the query into multiple ones?
Edit: as of now I have done this in more than one query (one to fetch the document, then I iterate over its state array to check if the ID exists in it, and then I perform (1), (2) and (3) in a normal if-else clause)
2
Answers
What you’re trying became possible with the introduction pipelined updates, here is how I would do it by using $concatArrays to concat the exists
state
array with the new input and $ifNull in case of an upsert to init the empty value, like so:Mongo Playground
Prior to version 4.2 and the introduction of this feature what you’re trying to do was not possible using the naive update syntax, If you are using an older version then you’d have to split this into 2 separate calls, first a
findOne
to see if the document exists, and only then an update based on that. obviously this can cause stability issue’s if you have high update volume.First thing FYI,
Second thing, you can achieve this in one query by using an update with aggregation pipeline in MongoDB 4.2,
postId
anduserId
fields onlystate
field under$set
stagestate
‘sid
?$map
to iterate loop ofstate
arrayid
andlvl: 0
?$mergeObjects
to merge current object with the new informationstate
array, by$concatArrays
operatorPlayrgound
Third thing, you can execute 2 update queries,
state
The second query on the base of if the response of the above query is
null
then this query will execute,id
andlvl: 0
conditions if conditions are fulfilled then execute the update fields operation, it will return null if the document is not found