Mongoose - remove multiple documents in one function call - node.js

In documentation there's deleteMany() method
Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, function (err) {});
I want to remove multiple documents that have one common property and the other property varies. Something like this:
Site.deleteMany({ userUID: uid, id: [10, 2, 3, 5]}, function(err) {}
What would be the correct syntax for this?

I believe what youre looking for is the $in operator:
Site.deleteMany({ userUID: uid, id: { $in: [10, 2, 3, 5]}}, function(err) {})
Documentation here: https://docs.mongodb.com/manual/reference/operator/query/in/

You can also use.
Site.remove({ userUID: uid, id: { $in: [10, 2, 3, 5]}}, function(err, response) {});

I had to change id to _id for it to work:
Site.deleteMany({ _id: [1, 2, 3] });
This happens if no id is defined and the default one is used instead:
"Mongoose assigns each of your schemas an _id field by default if one is not passed into the Schema constructor." mongoose docs

Yes, $in is a perfect solution :
Site.deleteMany({ userUID: uid, id: { $in: [10, 2, 3, 5] } }, function(err) {})

db.collectionName.deleteMany({key : value},function(err){
if(err){
console.log(err);
}
else{
console.log("All data deleted");
}
});

Related

Mongoose - Push value to array which is nested in object which is nested in array

unfortunately I can only find very specific questions on stackoverflow and no exact details in the documentation, so here is a more general example that may help others too.
I just want to add a value to the array (arr) in the object with the "title: 'title3".
{
_id: <id>,
prop1: val1,
prop2: val2,
prop3: [
{
title: 'title1',
arr: ['val1', 'val2', 'val3'],
},
{
title: 'title2',
arr: ['val1', 'val2', 'val3'],
},
{
title: 'title3',
arr: ['val1', 'val2', 'val3'], //only update this array
}
]
}
My current approach looks something like this:
SomeModel.findOneAndUpdate(
{ _id: id, "prop3.title": "title3" },
{$push: { "prop3.$[].arr": someDoc._id.toString() }},
(err, doc) => {
if (err) {
console.log('Error updating doc: ', err);
resolve(false);
} else {
resolve(doc);
}
}
);
However, the problem here is that a value is added not only in the array of the object with the title "title3", but everywhere.
How can I add a value exclusively to the array in the object with the title value "title3"?
I would also be very grateful for links to documentation explaining this.
You can use positional $ operator in this way to do it in a single operation:
Using $ you tell mongo "update the object found in the find stage". In this case "update the object where prop3.title is title3".
Note that you are using arrayFilters instead of positional operator.
db.collection.update({
"_id": 1,
"prop3.title": "title3"
},
{
"$push": {
"prop3.$.arr": "val4"
}
})
Example here
This is one solution which may or may not make sense in your full context.
You would have much more luck using the mongoose Document.save() method. Once you have a local copy of the document, you can simply push to the array:
const doc = await SomeModel.findOne({
_id: id,
"prop3.title": "title3"
});
doc.prop3[3].arr.push(item);
await doc.save();

How to update document with dynamic fields in node.js

Assume that I have a document in database which has the following structure;
id: 1,
name: alex,
age: 21
There are some updates in client side and the same document with id:1 returns as;
id: 1,
name: alex,
age: 21,
location: usa
where a new field location is added.
I want to update the document in database. I want to add this new inserted field but I couldn't find out how can I handle this situation. Because location is not static, it can be surname, job and so on. So new inserted fields are dynamic and I don't know in advance.
If the field location was static, I could write a query as;
db.collection("user").updateOne( {id: 1},
{$set: {name: "alex", age: "21", location: "usa"}}, function(err, result){
if(err){}
else{}
});
But now, how can I update the document with this newly added fields?
If you are saying that you have an updated object:
id: 1,
name: alex,
age: 21,
location: usa
then I assume that you have some object that has a value of:
{
id: 1,
name: 'alex',
age: 21,
location: 'usa'
}
e.g. as a result of parsing this JSON sent from the client:
{
"id": 1,
"name": "alex",
"age": 21,
"location": "usa"
}
If you have that object in a variable called object then all you have to do is:
db.collection("user")
.updateOne({id: object.id}, {$set: object}, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
But most likely you will need to use _id instead of id:
db.collection("user")
.updateOne({_id: object._id}, {$set: object}, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
If you want to be able to remove properties as well then maybe even:
db.collection("user")
.replaceOne({_id: object._id}, object, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
I wouldn't recommend putting data into the database without at least some sort of validation but it's possible to do what you're asking about in the way shown above.
For more info see:
Update Specific Fields
Replace a Document
in the Introduction to MongoDB in Node.js.

Handling concurrent modification w/ mongoose

I have a couple places in my application that uses mongoose where I need to handle concurrent modification.
I know there is a version '__v' in all my documents. Most everything I look at points back to this blog:
http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning
Which outlines using the __v field to protect against concurrent array modification within the document. ie:
posts.update({ _id: postId, __v: versionNumber }
, { $set: { 'comments.3.body': updatedText }})
Do mongoose use the __v field automatically in any way to validate documents when they are saved? Or do you always have to add it to your query statement?
After reviewing the Mongoose source codes, the __v is really only used for arrays, the VERSION_WHERE is set when updating the array path.
if ('$push' == op || '$pushAll' == op || '$addToSet' == op) {
self.$__.version = VERSION_INC;
}
// now handling $set, $unset
else if (/\.\d+\.|\.\d+$/.test(data.path)) {
// subpath of array
self.$__.version = VERSION_WHERE;
}
And per this answer,
var t = Test();
t.name = 'hi'
t.arr = [1, 2, 3, 4, 5, 6];
t.save(function (err, result) {
console.log(result);
// use Mongoose pull method on the array
t.arr.pull(3);
t.save(function(err2, result2) {
console.log(result2)
});
});
Results:
{ __v: 0,
name: 'hi',
_id: 53f59d2a6522edb12114b98c,
arr: [ 1, 2, 3, 4, 5, 6 ] }
{ __v: 1,
name: 'hi',
_id: 53f59d2a6522edb12114b98c,
arr: [ 1, 2, 4, 5, 6 ] }

MongoDB apply iterator with additional query to all results

Is there anyway within mongo, via MapReduce or Aggregation to apply a second query based on the result set of the first?, such as an Aggregate within an aggregate, or new emit/query within MapReduce.
For example, I have a materialized path pattern of items (which also includes parentId), I can get all of the roots simply by:
db.collection.find({parentId: null}
.toArray(function(err, docs) {
});
What I want to do is determine if these docs have children, just a flag true/false. I can iterate through these docs using async each and check, but on large docs, this is not very performant at all and causes event loop delays, I can use eachSeries, but this is just slow.
Ideally, I'd like to be able to handle this all within Mongo. Any suggestions if that's possible?
Edit, Example collection:
{
_id: 1,
parentId: null,
name: 'A Root Node',
path: ''
}
{
_id: 2,
parentId: 1,
name: 'Child Node A',
path: ',1'
}
{
_id: 3,
parentId: 2,
name: 'Child Node B',
path: ',1,2'
}
{
_id: 4,
parentId: null,
name: 'Another Root Node',
path: ''
}
This basically represents two root nodes, where one root node ({_id: 1}) has two children (one being direct), example:
1
2
3
4
What I would like to do is do a query based on parentId so I can get the root nodes by using null or by passing a parentId I can get the children of that and determine if the result set from this, any of the items contain children, example response for where {parentId: null}:
[{
_id: 1,
parentId: null,
name: 'A Root Node',
path '',
hasChildren: true
},
{
_id: 4,
parentId: null,
name: 'Another Root Node',
path '',
hasChildren: false
}]
You could try creating an array of the parentIds from the materialized paths that you can then use in the aggregation pipeline to project the extra field/flag hasChildren.
This can be done by using the map() method on the cursor returned from the find() method. The following illustrates this:
var arr = db.collection.find({ "parentId": { "$ne": null } })
.map(function (e){ return e.path; })
.join('')
.split(',')
.filter(function (e){ return e; })
.map(function (e){ return parseInt(e); }),
parentsIds = _.uniq(arr); /* using lodash uniq method to return a unique array */
Armed with this array of parentIds, you can then use the aggregation framework in particular the $project pipeline which makes use of the the set operator $setIsSubset which takes two arrays and returns true when the first array is a subset of the second, including when the first array equals the second array, and false otherwise:
db.collection.aggregate([
{
"$match": {
"parentId": null
}
},
{
"$project": {
"parentId": 1,
"name": 1,
"path": 1,
"hasChildren": { "$setIsSubset": [ [ "$_id" ], parentIds ] }
}
}
], function (err, res) { console.log(res); });

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