How can I update a document's nested array of object item by 1? - node.js

I'm trying to update a mongoose document with the help of findOneAndUpdate but I'm unable to do so. The document looks like this in the database:
{
'docId': 1001,
'totalViews': 3,
'docInfo': [
{
id: 1,
views: 2
},
{
id: 2,
views: 1
}
]
}
I'm trying to update totalViews by 1 which will make the total count to be 4. And I also need to update the second object's views property by 1 in imageInfo array. Which will have a views count of 2.
I tried doing this by first fetching the whole document with the help of:
const doc = await Doc.find({ docId: 1001 });
Then found the index of the docInfo array item which needs to be updated. Which is the object with id 2.
const docIndex = doc[0].docInfo.findIndex( item => {
return item.id === 2;
});
Then used findOneAndUpdate to update the items:
await Doc.findOneAndUpdate(
{ docId: 1001, "docInfo.id": 2 },
{
$set: {
[ `docInfo.${2}.views` ]: 1++,
'totalViews': 1++
}
}, { new: true }
);
With this I'm getting this error:
SyntaxError: Invalid left-hand side expression in postfix operation
What am I doing wrong here?

What you are doing is invalid, you can use $inc operator to increment a number, and don't need to find a query as well,
await Doc.findOneAndUpdate(
{ docId: 1001, "docInfo.id": 2 },
{
$inc: {
'docInfo.$.views': 1,
'totalViews': 1
}
},
{ new: true }
);
Playground

Related

MongoDB return nested array of objects minus a property within the object (also .then() doesn't work with aggregation)

I'm very new to mongodb and am having difficulty getting to a solution for my use case. For example I have the following document:
{
_id : ObjectId('5rtgwr6gsrtbsr6hsfbsr6bdrfyb'),
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A',
array: ['lots','off','stuff']
},
{
id : 2,
title: 'B',
array: ['even','more','stuff']
}
]
}
I'd like to have the following returned:
{
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A'
},
{
id : 2,
title: 'B'
}
]
}
I've tried various combinations of using findOne() and aggregate() with $slice and $project. With findOne(), if it worked at all, the who document would be returned. I am unable to test whether attempts at aggregating work because .then((ret)=>{}) promises don't seem to work in node.js for me with it (no issues with findOne). Calling a function like so
return db.myCollection.aggregate([
{
$match: {
_id:ObjectId(mongo_id)
}
},
{
$project : {
mainArray: {
id:1,
title:1
}
}
}
],function(err,res){
console.log(res)
return res
})
logs the entire function and not the droids I'm looking for.
You're missing toArray() method to obtain the actual result set. Instead you're returning the aggregation cursor object. Try this.
return db.myCollection.aggregate([matchCode,projectCode]).toArray().then(
data => {
console.log(data);
return data;
},
error => { console.log(error)});
The documnetation on aggregation cursor for MongoDB NodeJS driver can
be found here
http://mongodb.github.io/node-mongodb-native/3.5/api/AggregationCursor.html#toArray
This is an alternative solution (to the solution mentioned in the comment by #v1shva)
Instead of using aggregation you can use projection option of .findOne() operation.
db.myCollection.findOne(matchCode, {
projection: { _id: false, 'mainArray.array': false } // or { _id: -1, 'mainArray.array': -1 }
})

mongoose findOne, How do i find an element from an array of object?

I am trying to find and check if that user has a value on a certain item.
Sample model data:
user: {
_id: 1,
profilePictures: [
{
id: 1,
image: 'sample.png'
},
{
id: 2,
image: null
},
{
id: 3,
image: null
}
]
}
My Mongoose.find query:
const user = await userModel.findOne({
_id: 1,
profilePictures: {
$elemMatch: {
$and: [
{ id: 2, image: null },
{ id: 3, image: null }
]
}
}
})
but its returning nothing, I know i could just get the user and use Array.some() on profilePictures or some other Array function to check if an array item with position 2 and 3 has also a property image with a value of null
but since I'm learning Mongoose i am trying to find a way to do it that way.
Assuming you need to find the document for particular indexes, and the array is just one level, you could use this simple query
db.getCollection('somecollection').find({ "_id": 1, "profilePictures.1.image": null, "profilePictures.2.image": null })
So for now, I figured out a solution that doesn't use a complicated mongoose query.
I just validated it using JavaScript.
// req.body.id is an array of number
const user: any = await db.getCollection('somecollection').findOne({ _id: 1 })
let isAllPositionAvailable = true
for (const position of req.body.id) {
isAllPositionAvailable = user.profilePictures.find((x: any) => x.id === parseInt(position)).image === null
if (!isAllPositionAvailable) break
}
if (!isAllPositionAvailable) {
return res.status(400).send({
message: 'Image position is not available, please remove the existing image first'
})
}

Why is addToSet in mongodb not working in an array field?

The documents in the collection have an array field of sub-documents, each with a counter that should be increased up to three, but if the array doesn't have a sub-document with a given key it should create it with the default values.
The documentation for $addToSet says:
Behavior
$addToSet only ensures that there are no duplicate items added to the set and does not affect existing duplicate elements. $addToSet does not guarantee a particular ordering of elements in the modified set.
Missing Field
If you use $addToSet on a field is absent in the document to update, $addToSet creates the array field with the specified value as its element.
The problem is that the array field is not created in the document if it doesn't exist, as stated in the documentation.
This is what I'm currently using to accomplish the operation:
// increase counter in element of array if element exist and counter is less than 3
collection.updateOne({
key_1,
"array.key_2": key_2,
"array.counter": {$lt: 3}
}, {
$inc: {"array.$.counter": 1}
})
.then(res => {
console.log("!!!!1:", res.modifiedCount, res.upsertedId, res.upsertedCount, res.matchedCount);
if (res.matchedCount) return res.matchedCount;
// create element in array with the default values,
// also create the array field if it doesn't exist
collection.updateOne({
key_1
}, {
$addToSet: {
array: {key_2, counter: 1}
}
})
.then(res => {
console.log("!!!!2:", res.modifiedCount, res.upsertedId, res.upsertedCount, res.matchedCount);
return res.matchedCount;
})
.catch(e => console.log(e))
})
.catch(e => console.log(e))
Using upsert in the second query, it creates the array field if it doesn't exist but then when array.counter reaches 3 in subsequent calls to increase its value, the operation creates a new sub-document in the array with the same values for array.key_2 and array.date, effectible duplicating the entry although with array.counter set to 1 instead of 3.
I'm using mongo version 4.2.1
Update:
Below there is a sample document before trying to run the operation on the second subdocument:
{
"key_1": 1,
"array": [
{
"key_2": 1,
"counter" 1
}, {
"key_2": 2,
"counter" 3
}
]
}
This is what I'm getting as a result when using upsert:
{
"key_1": 1,
"array": [
{
"key_2": 1,
"counter" 1
}, {
"key_2": 2,
"counter" 3
}, {
"key_2": 2,
"counter" 1
}
]
}
The operation is duplicating the second subdocument in array, but if upsert is not used then the array field is not created if it's not already in the parent document, which is the oposite of the expected behavior for $addToSet from what it says in the documentation.
Update 2
These are the steps to reproduce the issue:
Run the operation with key_1 set to 1, and upsert disabled. None of the queries modifies the document. The array field is not created.
{
"key_1": 1
}
Enable upsert and run the operation again. The array field is created in the second query:
{
"key_1": 1,
"array": [
{
"key_2": 1,
"counter" 1
}
]
}
Run the operation again twice more. The first query modifies the document twice:
{
"key_1": 1,
"array": [
{
"key_2": 1,
"counter" 3
}
]
}
Run the operation once more. The first query doesn't modifies the document. The second query creates a duplicate:
{
"key_1": 1,
"array": [
{
"key_2": 1,
"counter" 3
}, {
"key_2": 1,
"counter" 1
}
]
}
Please try this :
var key_2Value = 2;
var firstFilterQuery = {
key_1: 1,
array: {
$elemMatch: {
"key_2": key_2Value,
"date": 'someDate',
"conter": { $lte: 3 }
}
}
}
var secondFilterQuery = {
key_1: 1,
"array.key_2": {$ne: key_2Value}
}
var defaultDoc = {key_2 : key_2Value, "date": 'someDefaultDate',counter: 1}
Query :
collection.bulkWrite([
{
updateOne:
{
"filter": firstFilterQuery,
"update": { $inc: { "array.$.conter": 1 } }
}
}, {
updateOne:
{
"filter": secondFilterQuery,
"update": { $push: { array: defaultDoc }
}
}
}
])
With the above query, you can achieve what you wanted in one DB call(at any given case only one 'updateOne' should update the DB), Output should look something like :
{
"acknowledged" : true,
"deletedCount" : 0.0,
"insertedCount" : 0.0,
"matchedCount" : 1.0,
"upsertedCount" : 0.0,
"insertedIds" : {},
"upsertedIds" : {}
}

Mongoose delete operation [duplicate]

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?
Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.
In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.
To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.

Mongoose, update values in array of objects

Is there a way to update values in an object?
{
_id: 1,
name: 'John Smith',
items: [{
id: 1,
name: 'item 1',
value: 'one'
},{
id: 2,
name: 'item 2',
value: 'two'
}]
}
Lets say I want to update the name and value items for item where id = 2;
I have tried the following w/ mongoose:
var update = {name: 'updated item2', value: 'two updated'};
Person.update({'items.id': 2}, {'$set': {'items.$': update}}, function(err) { ...
Problem with this approach is that it updates/sets the entire object, therefore in this case I lose the id field.
Is there a better way in mongoose to set certain values in an array but leave other values alone?
I have also queried for just the Person:
Person.find({...}, function(err, person) {
person.items ..... // I might be able to search through all the items here and find item with id 2 then update the values I want and call person.save().
});
You're close; you should use dot notation in your use of the $ update operator to do that:
Person.update({'items.id': 2}, {'$set': {
'items.$.name': 'updated item2',
'items.$.value': 'two updated'
}}, function(err) { ...
model.update(
{ _id: 1, "items.id": "2" },
{
$set: {
"items.$.name": "yourValue",
"items.$.value": "yourvalue",
}
}
)
MongoDB Document
There is a mongoose way for doing it.
const itemId = 2;
const query = {
item._id: itemId
};
Person.findOne(query).then(doc => {
item = doc.items.id(itemId );
item["name"] = "new name";
item["value"] = "new value";
doc.save();
//sent respnse to client
}).catch(err => {
console.log('Oh! Dark')
});
There is one thing to remember, when you are searching the object in array on the basis of more than one condition then use $elemMatch
Person.update(
{
_id: 5,
grades: { $elemMatch: { grade: { $lte: 90 }, mean: { $gt: 80 } } }
},
{ $set: { "grades.$.std" : 6 } }
)
here is the docs
For each document, the update operator $set can set multiple values, so rather than replacing the entire object in the items array, you can set the name and value fields of the object individually.
{'$set': {'items.$.name': update.name , 'items.$.value': update.value}}
Below is an example of how to update the value in the array of objects more dynamically.
Person.findOneAndUpdate({_id: id},
{
"$set": {[`items.$[outer].${propertyName}`]: value}
},
{
"arrayFilters": [{ "outer.id": itemId }]
},
function(err, response) {
...
})
Note that by doing it that way, you would be able to update even deeper levels of the nested array by adding additional arrayFilters and positional operator like so:
"$set": {[`items.$[outer].innerItems.$[inner].${propertyName}`]: value}
"arrayFilters":[{ "outer.id": itemId },{ "inner.id": innerItemId }]
More usage can be found in the official docs.
cleaner solution using findOneAndUpdate
await Person.findOneAndUpdate(
{ _id: id, 'items.id': 2 },
{
$set: {
'items.$.name': 'updated item2',
'items.$.value': 'two updated',
}
},
);
In Mongoose, we can update array value using $set inside dot(.) notation to specific value in following way
db.collection.update({"_id": args._id, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
Having tried other solutions which worked fine, but the pitfall of their answers is that only fields already existing would update adding upsert to it would do nothing, so I came up with this.
Person.update({'items.id': 2}, {$set: {
'items': { "item1", "item2", "item3", "item4" } }, {upsert:
true })
I had similar issues. Here is the cleanest way to do it.
const personQuery = {
_id: 1
}
const itemID = 2;
Person.findOne(personQuery).then(item => {
const audioIndex = item.items.map(item => item.id).indexOf(itemID);
item.items[audioIndex].name = 'Name value';
item.save();
});
Found this solution using dot-object and it helped me.
import dot from "dot-object";
const user = await User.findByIdAndUpdate(id, { ...dot.dot(req.body) });
I needed to update an array element with dynamic key-value pairs.
By mapping the update object to new keys containing the $ update operator, I am no longer bound to know the updated keys of the array element and instead assemble a new update object on the fly.
update = {
name: "Andy",
newKey: "new value"
}
new_update = Object.fromEntries(
Object.entries(update).map(
([k, v], i) => ["my_array.$." + k, v]
)
)
console.log({
"$set": new_update
})
In mongoose we can update, like simple array
user.updateInfoByIndex(0,"test")
User.methods.updateInfoByIndex = function(index, info) ={
this.arrayField[index]=info
this.save()
}
update(
{_id: 1, 'items.id': 2},
{'$set': {'items.$[]': update}},
{new: true})
Here is the doc about $[]: https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S[]

Resources