How to upsert with MongoDB array? - arrays

I am trying to insert, update values in the MongoDB array.
My MongoDB version is 4.0.5.
Here is my collection :
{
'id': 1,
'array': [{
'code': 'a'
}, {
'code': 'b'
}]
}
I am trying to make some upsert queries to insert/update into an array but I don't found a good solution until then.
My filters are :
'id' (to point the correct document)
'array.code' (to point the correct array cell)
If the document exists in the collection but there is no cell with 'code': 'c'
db.test.update({
'id': 1
}, {
$set: {'array.$[elem].test':'ok'}
}, {
upsert: true,
arrayFilters: [{'elem.code': 'c'}]
}
)
I have no error but no upsert too.
I want to insert the element in the array like this :
// Desired result
{
'id': 1,
'array': [{
'code': 'a'
}, {
'code': 'b'
}, {
'code': 'c'
'test': 'ok'
}]
}
If the document doesn't exist in the collection
db.test.update({
'id': 3
}, {
$set: {'array.$[elem].test':'ok'}
}, {
upsert: true,
arrayFilters: [{'elem.code': 'a'}]
}
)
In that case, I have this error :
WriteError: The path 'array' must exist in the document in order to apply array updates., full error: {'index': 0, 'code': 2, 'errmsg': "The path 'array' must exist in the document in order to apply array updates."}
I want to upsert a new document with elements of the query like this :
// Desired result
{
'id': 3,
'array': [{
'code': 'a'
'test': 'ok'
}]
}
upsert: true in the query parameters doesn't seem to work with the array.
Your help will be highly appreciated.

The upsert is not effective in the array, If you do update MongoDB version from 4.0.5 to 4.2 then you can able to use update with aggregation pipeline starting from MongoDB 4.2,
Case 1: If the document exists in the collection but there is no cell with 'code': 'c':
var id = 2;
var item = { code: "c", test: "ok" };
Playground
Case 2: If the document doesn't exist in the collection:
var id = 3;
var item = { code: "a", test: "ok" };
Playground
$ifNull to check if the field does not exist then return empty
$cond to check if input code is in array
yes, then $mep to iterate loop of array and check condition if match then update other fields otherwise return an empty object
$mergeObjects to merge current object with updated fields
no, $concatArrays to concat current array with new item object
db.collection.update(
{ "id": id },
[{
$set: {
array: {
$cond: [
{
$in: [item.code, { $ifNull: ["$array.code", []] }]
},
{
$map: {
input: "$array",
in: {
$mergeObjects: [
"$$this",
{
$cond: [{ $eq: ["$$this.code", "c"] }, item, {}]
}
]
}
}
},
{
$concatArrays: [{ $ifNull: ["$array", []] }, [item]]
}
]
}
}
}],
{ upsert: true }
)

Related

MongoDb aggregate addField if an array of object contains object with specific attribute

I need to add a completed attribute using addField aggregate query, if the orders array inside a collection (sales) contains an object with status attribute having value delivered.
db.sales.insertMany([
{
"_id" : 1,
"salesId" : "2938432",
"customerId" : "987677789",
"amount:"23000"
"orders":[{
orderId:"TX8383428979382",
status:"cancelled"
},
{
orderId:"TX8383428979383",
status:"fullfilled"
}]
},
{
"_id" : 1,
"salesId" : "2938433",
"customerId" : "987676578",
"amount:"13000"
"orders":[
{
orderId:"TX838342892437363",
status:"placed"
}]
},
.........
)]
My aggregate query
db.sales.aggregate([
{$match:{....}},
{$addFields:{
completed: {
$cond: { if: { /* code to check if orders array contains an object with `status:fullfilled` }, then: true, else: false }
}
}},
])
How can I check for a match for an attribute inside array of objects.?
Here is the aggregation which uses the $reduce array operator to add a new attribute completed which is derived based upon the orders.status "delivered".
db.sales.aggregate( [
{
$addFields: {
completed: {
$reduce: {
input: "$orders", initialValue: false,
in: {
$cond: [ { $eq: [ "$$this.status", "delivered" ] },
true,
"$$value"
]
}
}
}
}
}
] ).pretty()

How can I update a multi level nested array in MongoDB?

How can I update a record in a document with multiple levels of array nesting?
My document structure is the following:
{
"_id": "5bfa09f0a0441f38d45dcc9c",
"nombre": "PROYECTO MAIN",
"area": "Sistemas",
"fecha": "27/01/2018",
"reuniones": [
{
"_id": "5bfa09f0a0441f38d45dcc99",
"objetivo": "Objetivo MODIFICADO",
"fecha": "25/10/2018",
"participantes": [
{
"nomina": 1,
"nombre": "MODIFICADO",
"rol": "rol",
"area": "area",
"firma": "url/imagen.jpg"
},
{
"nomina": 2,
"nombre": "nombre 2",
"rol": "rol",
"area": "area",
"firma": "url/imagen.jpg"
}
]
}
],
"_class": "proyecto"
}
Using the following query, returns me the document mentioned above.
db.proyectos.find({
_id:ObjectId("5bfa09f0a0441f38d45dcc9c"),
"reuniones._id":ObjectId("5bfa09f0a0441f38d45dcc99"),
"reuniones.participantes.nomina":2
})
I want to update the firma field of participant with nomina 2.
Since Mongo 3.6, you can update multi-nested arrays by combining the following operators:
$set (to update a specific field)
$[] (to match any item in an array)
$[<identifier>] (to match specific items in an array)
Example
Here's how you can update a specific proyectos document that has a reuniones array that has a participantes array that has an object with the field nomina equal to 2:
// update a specific proyectos document
// that has a field "reuniones" which is an array
// in which each item is an object with a field "participantes" that is an array
// in which each item is an object that has a field "nomina" equal to 2
db.proyectos.update({
_id: ObjectId("5bfa09f0a0441f38d45dcc9c"),
}, {
$set: {
"reuniones.$[].participantes.$[j].firma": <your update>
},
}, {
arrayFilters: [
{
"j.nomina": 2
}
]
})
If you wanted to limit your query to a specific reunion, you can do:
db.proyectos.update({
_id: ObjectId("5bfa09f0a0441f38d45dcc9c"),
}, {
$set: {
"reuniones.$[i].participantes.$[j].firma": <your update>
},
}, {
arrayFilters: [
{
"i._id": ObjectId("5bfa09f0a0441f38d45dcc99")
},
{
"j.nomina": 2
}
]
})
To update all proyectos satisfying the above condition, just omit the _id query:
// update all proyectos
// that have a field "reuniones" which is an array
// in which each item is an object with a field "participantes" that is an array
// in which each item is an object that has a field "nomina" equal to 2
db.proyectos.update({}, {
$set: {
"reuniones.$[].participantes.$[j].firma": <your update>
},
}, {
arrayFilters: [
{
"j.nomina": 2
}
]
})

Adding Property to Documents Where Target Property is an Array Not Working as Expected

I am trying to add a property to every element in an array for a property on every document in a collection. After doing some research, this seems like the way to go:
db.customers.update(
{ "subscriptions": { "$elemMatch": { "subscriptions._id" : { $exists: true } } } },
{ "$set": { "subscriptions.deleted": false } },
{ "multi": true }
)
What I assume this will do is find every element on the "subscriptions" array where an "_id" prop exists (which will be every one). It will then add the "deleted" property to each one of those elements -- and do that for all documents since "multi" is set to "true".
But this doesn't seem to work as expected. My result is:
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
Is there something I'm missing here?
This is what the data looks like:
{
_id: 333,
nameFirst: 'John',
nameLast: 'Smith',
subscriptions: [
{ _id: 555,
someProp: 'abc'
},
{ _id: 556,
someProp: 'def'
},
],
email: 'john#email.com'
}
... and this is what I'm trying to accomplish:
{
_id: 333,
nameFirst: 'John',
nameLast: 'Smith',
subscriptions: [
{ _id: 555,
someProp: 'abc',
deleted: false // This is the prop I want to add to each element
},
{ _id: 556,
someProp: 'def',
deleted: false // This is the prop I want to add to each element
},
],
email: 'john#email.com'
}
You can use the $[identifier] (positional filtered) operator in MongoDB 3.6 to update multiple array elements specifying matching condition:
db.customers.update(
{ },
{ "$set": { "subscriptions.$[cond].deleted": false } },
{ "multi": true, arrayFilters: [{ "cond._id": { $exists: true } }] }
)
For lower MongoDB version you can use the $out operator which can replace existing collection with aggregation result. Try:
db.customers.aggregate([
{
$addFields: {
subscriptions: {
$map: {
input: "$subscriptions",
as: "sub",
in: {
$cond: {
if: { $gt: [ "$$sub._id", null] },
then: { _id: "$$sub._id", someProp: "$$sub.someProp", deleted: false },
else: "$$sub"
}
}
}
}
}
},
{ $out: "customers" }
])
Using $map and $addFields to overwrite existing subscriptions and $gt to check if field exists.

Push object into array if exists otherwise set object in MongoDB

This is the document I currently have:
{
"_id": "",
"title": "My Watchlist",
"series": [{
"seriesId": 1,
"following": true,
"seasons": []
}, {
"seriesId": 1,
"following": false,
"seasons": []
}]
}
As you can see there are currently 2 objects with the seriesId 1, but with a different following boolean.
If the query matches with _id it should push the new object into series, if within the "series"-array an object with the same "seriesId" already exists it should change the fields within that object, instead of adding a new object.
I currently have the following query:
users.update(
{"_id": req.body.userId},
{
"$push": {
"series": {"seriesId": req.body.seriesId, "following": req.body.following}
}
}, (err, data) => {
if (err)
next(err);
});
If I use $set it does not add the object if it didn't originaly exist yet, and as far as I know you cannot both use $push and $set?
Can this be fixed in any way or do I have to rethink my Schema?
You can use two update query :
if _id is found and seriesId is not in the array, add the new item to the array :
db.series.update({
"_id": req.body.userId,
"series": {
"$not": {
"$elemMatch": {
"seriesId": req.body.seriesId
}
}
}
}, {
$addToSet: {
series: {
"seriesId": req.body.seriesId,
"following": req.body.following,
"seasons": []
}
}
}, { multi: true });
if _id is found and seriesId is found in the array, update the array item :
db.series.update({
"_id": req.body.userId,
"series.seriesId": req.body.seriesId
}, {
$set: {
"series.$.following": req.body.following
}
}, { multi: true });

Insert object into MongoDB array element

I have to insert an object in every array in the MongoDB document. The items array displayed below has itemList; in every itemList I have to insert a itemSpec. The desired document shape before and after the process is shown below:
Before process
{
"items": [
{
"itemList":{
"rejected": true
},
"fProcBy": "automatic"
},
{
"itemList":{
"rejected": true
},
"fProcBy": "automatic"
}
]
}
After process:
{
"items": [
{
"itemList":{
"rejected": true
},
"itemSpec":{
"approved": true
},
"fProcBy": "automatic"
},
{
"itemList":{
"rejected": true
},
"itemSpec":{
"approved": true
},
"fProcBy": "automatic"
}
]
}
So in each element of the items array there has to be inserted a new object property itemSpec.
I am not aware of a solution which does it in a single run, but with the following simple script the maximal number of iterations is equal to the maximal number of array elements you have in a single documents, which is probably not so high (but it's just a guess as I have no further information about your data):
var q = { "items.itemSpec": null };
var u = {
$set: {
"items.$.itemSpec" : {
"approved": true
}
}
};
var yourColl = db.getCollection('fooBar');
while (yourColl.find(q).count() > 0) {
yourColl.update(q, u, { multi: true });
}
Use $push operator to update array in a document.
Try This:
db.test.update({"_id":"yourUniqueId"},
{$push: { "items":{ "itemSpec":{"approved": true}}}});

Resources