Update multiple elements with different value in Mongoose - node.js

I have document containing lists. Lets say they are:
[
{
_id: 52b37432b2395e1807000008,
name: ListA,
order: 1,
desc: 'More about List A'
},
{
_id: 52b37432b2395e1807000009,
name: LISTB,
order: 2,
desc: 'More about List B'
},
{
_id: 52b37432b2395e180700000e,
name: LISTC,
order: 3,
desc: 'More about List C'
},
{
..
}
]
Now I want to change their order field using a batch update. I have a JSON of updated_stage order
var updated_stage = [{_id: '52b37432b2395e1807000008', order:2},{_id: '52b37432b2395e180700000e', order:1}, {_id: '52b37432b2395e1807000009', order:3}]
Now I need to update LIST Model in Mongoose with the new array that I have. I know that I can update multiple documents with same value using Batch Update
Model.update({ }, { $set: { order: 10 }}, { multi: true }, callback);
But I have to update them by different values. How should I do it? Whats the most efficient way?

The most efficient way I could think of is to run a forEach loop over your updated_stage array.
Now take the _id and update order in the existing document in MongoDB.

Here my test with collection.forEach then call doc.save:
I use sync.each to know when all documents is save
var mongoose = require('mongoose'), async = require('async');
mongoose.connect('localhost', 'learn-mongoose');
var User = mongoose.model('User', {name: String});
async.series([
function (done) {
// remove User collection if exist
User.remove(done);
},
function(done) {
// re-create a collection with 2 users 'Mr One', 'Mr Two'
User.create([{name: 'Mr One'}, {name: 'Mr Two'}], done);
},
function(done) {
// upperCase user.name
User.find(function(err, users) {
async.each(users, function(user, callback) {
user.name = user.name.toUpperCase();
user.save(callback);
}, done); // done is call when all users are save!!!!
});
},
function(done) {
// print result
User.find(function(err, users) {
console.log(users);
done();
});
},
], function allTaskCompleted() {
console.log('done');
mongoose.disconnect();
});

You guys can use mongoose/mongodb bulkwrite function.
Reference

Related

How can I find specific document and update a value of specific key inside array?

I have a structure like this:
{
_id: new ObjectId("634aa49f98e3a05346dd2327"),
filmName: 'Film number 1',
episodes: [
{
episodeName: 'Testing 1',
slugEpisode: 'testing-1',
_id: new ObjectId("6351395c17f08335f1dabfc9")
},
{
episodeName: 'Testing 2',
slugEpisode: 'testing-2',
_id: new ObjectId("6351399d9a2533b9be1cbab0")
},
],
},
{
_id: new ObjectId("634aa4cc98e3a05346dd232a"),
filmName: 'Film number 2',
episodes: [
{
episodeName: 'Something 1',
slugEpisode: 'something-1',
_id: new ObjectId("6367cce66d6b85442f850b3a")
},
{
episodeName: 'Something 2',
slugEpisode: 'something-2',
_id: new ObjectId("6367cd0e6d6b85442f850b3e")
},
],
}
I received 3 fields:
_id: Film _id
episodeId: Episode _id
episodeName: The content I wish to update
I tried to find a specific Film ID to get a specific film, and from then on, I pass an Episode ID to find the exact episode in the episodes array. Then, update the episodeName of that specific episode.
Here's my code in NodeJS:
editEpisode: async (req, res) => {
const { _id } = req.params
const { episodeId, episodeName } = req.body
try {
const specificResult = await Films.findOneAndUpdate(
{ _id, 'episodes._id': episodeId },
{ episodeName }
)
console.log(specificResult)
res.json({ msg: "Success update episode name" })
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
But what console.log display to me is a whole document, and when I check in MongoDB, there was no update at all, does my way of using findOneAndUpdate incorrect?
I'm reading this document: MongooseJS - Find One and Update, they said this one gives me the option to filter and update.
The MongoDB server needs to know which array element to update. If there is just one array element to update, here's one way you could do it. (I picked a specific element. You would use your req.params and req.body.)
db.films.update({
"_id": ObjectId("634aa4cc98e3a05346dd232a"),
"episodes._id": ObjectId("6367cd0e6d6b85442f850b3e")
},
{
"$set": {
"episodes.$.episodeName": "Something Two"
}
})
Try it on mongoplayground.net.
You can use the filtered positional operator $[<identifier>] which essentially finds the element or object (in your case) with a filter condition and updates that.
Query:
const { _id } = req.params
const { episodeId, episodeName } = req.body
await Films.update({
"_id": _id
},
{
$set: {
"episodes.$[elem].episodeName": episodeName
}
},
{
arrayFilters: [
{
"elem._id": episodeId
}
]
})
Check it out here for example purpose I've put ids as numbers and episode name to update as "UpdatedValue"

For each MongoDB document, add a new variable that increments

I have a collection with the following documents:
[{_id: abc, name: "foo"}, {_id: def, name: "bar"}, {_id: ghi, name: "baz"}]
I want to change every document in that collection so it has a new field, which is unique, and that has a letter and a number, the number increases with each document. So I want to have this:
[{_id: abc, name: "foo", customId: "m1"}, {_id: def, name: "bar", customId: "m2"}, {_id: ghi, name: "baz", customId: "m3"}]
I tried using the most voted answer in this question, but it only has a number which is kind of the index in the array, but I want a letter and the number next to it.
I am using NodeJS and Express with the mongoose package. I don't mind if the answer is either using javascript code or a mongo cli command.
Any help is very appreciated, thanks in advance.
I'm assuming you need to update the existing table and also need to create the counter field for the upcoming data's,
function update() { //updating existing table
user.aggregate(
[{
$match: {
"counter": { $exists: false }
}
}],
function (err, res) {
if (err) {
console.log(err)
}
var i = 0;
var newId;
res.forEach((element, index) => {
i = i + 1;
newId = "count" + i
user.update(
{ id: element.id },
{ $set: { "Counter": newId } }
);
});
})
}
function create(userparam) {//while creating new table
autonumber.find({}, function (err, res) {
let counter_value = "Count" + res[0].incrementer
//assuming incrementer to be feild in autonumber table
const user = new User(userparam);
user.Counter = counter_value;
return await user.save()
})
}
I'm beginner,so if this code is inefficient or wrong .... sorry in advance.

mongodb findOneAndUpdate replace document instead of updating specified field [duplicate]

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

Mongoose overwrite the document rather that `$set` fields

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

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