For each MongoDB document, add a new variable that increments - node.js

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.

Related

Cannot find id and update and increment sub-document - returns null Mongoose/mongoDB

I have a problem where I cannot seem to retrieve the _id of my nested objects in my array. Specifically the foods part of my object array. I want to find the _id, of lets say risotto, and then increment the orders count dynamically (from that same object).
I'm trying to get this done dynamically as I have tried the Risotto id in the req.body._id and thats fine but i can't go forward and try to increment orders as i get null.
I keep getting null for some reason and I think its a nested document but im not sure. heres my route file and schema too.
router.patch("/update", [auth], async (req, res) => {
const orderPlus = await MenuSchema.findByIdAndUpdate({ _id: '5e3b75f2a3d43821a0fb57f0' }, { $inc: { "food.0.orders": 1 }}, {new: true} );
//want to increment orders dynamically once id is found
//not sure how as its in its own seperate index in an array object
try {
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});
Schema:
const FoodSchema = new Schema({
foodname: String,
orders: Number,
});
const MenuSchema = new Schema({
menuname: String,
menu_register: Number,
foods: [FoodSchema]
});
Heres the returned Database JSON
{
"_id": "5e3b75f2a3d43821a0fb57ee",
"menuname": "main course",
"menu_register": 49,
"foods": [
{
"_id": "5e3b75f2a3d43821a0fb57f0",
"foodname": "Risotto",
"orders": 37
},
{
"_id": "5e3b75f2a3d43821a0fb57ef",
"foodname": "Tiramisu",
"orders": 11
}
],
"__v": 0
}
the id for the menuname works in its place but i dont need that as i need to access the foods subdocs. thanks in advance.
You are sending food id (5e3b75f2a3d43821a0fb57f0) to the MenuSchema.findByIdAndUpdate update query. It should be the menu id which is 5e3b75f2a3d43821a0fb57ee
You can find a menu by it's id, and update it's one of the foods by using food _id or foodname using mongodb $ positional operator.
Update by giving menu id and food id:
router.patch("/update", [auth], async (req, res) => {
try {
const orderPlus = await MenuSchema.findByIdAndUpdate(
"5e3b75f2a3d43821a0fb57ee",
{ $inc: { "foods.$[inner].orders": 1 } },
{ arrayFilters: [{ "inner._id": "5e3b75f2a3d43821a0fb57f0" }], new: true }
);
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});
Update by giving menu id and foodname:
router.patch("/update", [auth], async (req, res) => {
try {
const orderPlus = await MenuSchema.findByIdAndUpdate(
"5e3b75f2a3d43821a0fb57ee",
{ $inc: { "foods.$[inner].orders": 1 } },
{ arrayFilters: [{ "inner.foodname": "Risotto" }], new: true }
);
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});

Express Route with /:id input returns empty array

I am trying to have my API take an id as input and return results from mongoDB according to the id given.
My example collection looks like this:
id: 1 {
count: 5
}
id: 2 {
count: 10
}
My mongoose Schemas looks like this:
var tripSchema = new Schema({
_id: Number,
count: Number
},
{collection: 'test'}
);
And I created another file for this route, where I think the error lies in:
module.exports = function(app) {
app.get('/trips/:id', function(req,res) {
console.log(req.params.id); // Does print the ID correctly
var aggr = Trip.aggregate([
{ "$match": {
"_id": {
"$eq": req.params.id
}
}
},
{
"$project": {
"_id" : 1,
"count": "$count"
}
}
])
aggr.options = { allowDiskUse: true };
aggr.exec(function(err, stations){
if(err)
res.send(err);
res.json(stations);
});
});
}
Now using postman I try to GET /trips/72, but this results in an empty array [], there is an entry in the DB for _id 72 with a corresponding count just like above. My question is if this is the correct approach and what I am doing wrong here.
--Update:
There seems to be something wrong with either the match stage or the whole aggregation. I opted for mongoose's findById, and with this it works now:
Trip.findById(req.params.id, function (err, doc){
res.json(doc);
});
req.params.id returns your id in String form, while I think in aggregate match section you need to pass it as ObjectId. So, you should convert it to ObjectId:
$match: { _id: ObjectId(req.params.id) }

Mongoose avg function by request field

I want to return the average of a Number field by another field(the document ID field):
Comments.aggregate([
{$group:
{
_id: ($nid: req.adID), // here is the field I want to set to req.adID
adAvg:{$avg:"$stars"}
}
}
], function(err, resulat){
if(err) {
res.send(String(err));
}
res.send(resulat);
}
)
The ID field is in the request object, req.adID, I didnt find an example for grouping by a query (_id : 'req.adID').
My schema looks like:
var CommentSchema = new Schema(
{
_id: Schema.Types.ObjectId, //scheme
nid:Number, // ad ID
posted: Date, // Date
uid: Number, // user ID
title: String, //String
text: String, // String
stars: Number //String
}
);
Also if someone can write the return data for this query it will be great!
From your follow-up comments on the question, looks like your aggregation needs the $match pipeline to query the documents that match the req.adID on the nid field, your $group pipeline's _id field should have the $nid field expression so that it becomes your distinct group by key. The following pipeline should yield the needed result:
var pipeline = [
{ "$match": { "nid": req.adID } },
{
"$group": {
"_id": "$nid",
"adAvg": { "$avg": "$stars" }
}
}
]
Comments.aggregate(pipeline, function(err, result){
if(err) {
res.send(String(err));
}
res.send(result);
})

Update multiple elements with different value in Mongoose

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

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