I\'ve been trying to get this running for a while now but I can\'t figure out what I\'m doing wrong.
I have two schemas like this
const paymentSchema = n
Consider the two input documents. The first one will be inserted (the year_month: '2020_03'
doesn't exist in the payments
array). When the update is run with the second one it will update the status
of the existing sub-document in the array.
The update operation is valid with the MongoDB version 4.2 or later only, as it uses a pipeline for the update.
INPUT_DOC = { year_month: '2020_03', status: false } // this will get inserted into the array
INPUT_DOC = { year_month: '2020_02', status: true } // updates the sub-document
db.collection.findOneAndUpdate(
{
_id: "5e90ae0e0ed9974174e92826"
},
[
{
$set: {
payments: {
$reduce: {
input: "$payments",
initialValue: { payments: [], update: false },
in: {
$cond: [ { $eq: [ "$$this.year_month", INPUT_DOC.year_month ] },
{
payments: {
$concatArrays: [
[ { _id: "$$this._id", year_month: "$$this.year_month", status: INPUT_DOC.status } ],
"$$value.payments"
]
},
update: true
},
{
payments: {
$concatArrays: [ [ "$$this" ], "$$value.payments" ]
},
update: "$$value.update"
}
]
}
}
}
}
},
{
$set: {
payments: {
$cond: [ { $eq: [ "$payments.update", false ] },
{ $concatArrays: [ [ INPUT_DOC ], "$payments.payments" ] },
{ $concatArrays: [ [ ], "$payments.payments" ] }
]
}
}
}
],
{
new: true,
upsert: true
}
)
The main problem is that findOneAndUpdate
does exactly what its name implies. It executes a find
using the provided filter, and if a match is found, applies the updates to the first matching document.
If the collection contains only this document:
[
{
"_id": "5e90ae0e0ed9974174e92826",
"payments": [
{
"year_month": "2020_02",
"status": false
}
]
}
]
The initial find part is essentially
.find({
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
})
This matches nothing, and since upsert is set to true, fineOneAndUpdate attempts to create a brand new document. Even if it were able to create an array from an unmatched positional operator, the document it would be trying to add would be:
{
"_id": "5e90ae0e0ed9974174e92826",
"payments": [
{
"year_month": "2020_03",
"status": false
}
]
}
This is not correct, and would fail to insert due to duplicate _id
value anyway.
If you are using MongoDB 4.2, you could use an aggregation pipeline as the second argument to findAndUpdate
to check the array for the element you are interested in and add it if it is missing.
One not very pretty method is below. The findOneAndUpdate will match the _id, and the pipeline will:
- check if any element in the array matches the desired year_month
- If so, $reduce the array to update the status field in that element
- If not, append a new element
- Assign the result back to payments
.findOneAndUpdate(
{ "_id": "5e90ae0e0ed9974174e92826" },
[{$set: {
payments: {$cond:[
{$gt:[
{$size:
{$filter:{
input:"$payments",
cond:{$eq:["$$this.year_month","2020_03"]}
}}},
1
]},
{$reduce:{
input:"$payments",
initialValue:[],
in:{$concatArrays:[
"$$value",
[{$cond:[
{$eq:["$$this.j",3]},
{$mergeObjects:["$$this",{status:true}]},
"$$this"
]}]
]}
}},
{$concatArrays:[
"$payments",
[{year_month:"2020_03", status:true}]
]}
]}
}}]
)