$inc with mongoose ignored - node.js

I am using the following code to add some content in an array and increment two different counters.
The item is properly pushed in the array, and the pendingSize is properly incremented. But the unRead is never incremented. It used to increment, then today it stopped. The value of the unRead field in my mongodb collection ( hosted on mongohq ) is set to 0 (numerical, not string)
When I look in my console, I see 'update success'.
any clue why it stopped working ?
Thanks
Notifications.update({ "id" : theid}, { $push: { pending: notification}, $inc: { unRead : 1 }, $inc: { pendingSize: 1 }}, function(err){
if(err){
console.log('update failed');
callback("Error in pushing." + result.members[i]);
}
else{
console.log('update succes');
callback("Success");
}
});

Combine the args of $inc into a single nested object, like this:
$inc: { unRead : 1, pendingSize: 1 }
Objects represented in JSON are key:value, where the keys must be unique, so trying to specify multiple values for $inc won't work.

Related

How to use $inc operator for variables in MongoDB using Node.JS

I am trying to build a "number of visitors" collection in mongoDb using Node.JS backend of my website. The frontend sends the following info to Node.JS backend as JSON.
isUniqueVisitor - 1 if yes, 0 if no
country - standard country code - "JP", "IN", "UK", etc
My database looks like following
{
"today": 2019-06-07,
"uniqueVisitors": {
"count": 230,
"countries": {
"JP": 102,
"IN": 88,
"UK": 30
}
}
}
It works well if I use $inc with fixed values
Eg. $inc: {count: 1} // for string/integers keys
Eg. $inc: {"uniqueVisitors.count": 1} // inside quotes to access key of a JSON
Main issue:
I am not able to access a document name using variable.
Eg. $inc: {`uniqueVisitors.countries[${req.body.country}]`}
This creates an error as backticks can't be used for Mongo.
I tried with
Eg. $inc: {uniqueVisitors["countries"][req.body.country]}
But even this creates error.
I followed the web and found that mongo $set using variables can be realized by passing the required JSON directly to $set. Hence I resorted to code it the following way.
mongoClient.connect(mongoURL, async function (err, db) {
if (err) throw err;
console.log("Database connected");
// Identifying my document with today's date
var myQuery = {
date: getTodayDate()
};
// Defining the JSON to be passed to uniqueVisitors $inc
var uniqueVisitorsInc = {
"uniqueVisitors": {
"count": 0,
"countries": {}
}
};
// Populating the JSON to be passed to uniqueVisitors $inc => essentially asking to increase count by 1 and increase that country's count by 1
uniqueVisitorsInc["uniqueVisitors"]["count"] = 1;
uniqueVisitorsInc["uniqueVisitors"]["countries"][myData.country] = 1;
var newValues = {
$inc: uniqueVisitorsInc
};
await db.collection("visitorStats").update(myQuery, newValues, {upsert: true});
db.close();
});
The above method worked well on editor but threw the following runtime error:
$inc requires numerical values
Basically asking me to pass values to $inc in {var1: 1, var2: 5} pattern.
Please help me bypass this weird situation.
I know I can do a two step process where I read the values first, increment in variable and $set it in Mongo.
But does anyone know how to overcome this situation using $inc?
If this update were hardcoded to update "JP" only, it'd need to look like:
$inc: { "uniqueVisitors.country.JP": 1 }
So you were almost there with the backtick method but change the syntax a bit and keep the : 1 part like so:
$inc: { [`uniqueVisitors.country.${req.body.country}`]: 1 }

How to update a collection document using multiple criteria

I have this document in collection "registosSRS":
{
"_id" : ObjectId("5a3a2b47d04b7e07f8a273dc"),
"sessao" : "5",
"valorRelacao" : "4.89",
"valorObjectivo" : "4.97",
"valorAbordagem" : "4.88",
"valorGeral" : "4.92",
"cliente_id" : "5a1407c8099ca208e48170a5",
"email" : "mgoncalves#psi.uminho.pt",
"data" : 1513761607431
}
and this document in collection sessao. I've created this document in another place, and set dadosSRS to {} because I want to later change this value. Is this possible to add this value without having created it?
"_id" : ObjectId("5a3a2b41d04b7e07f8a273db"),
"cliente" : ObjectId("5a1407c8099ca208e48170a5"),
"data" : 1513761601705,
"numero" : "5",
"dadosORS" : ObjectId("5a3a2b41d04b7e07f8a273da"),
"dadosSRS" : {
}
Then I'm looking in collection sessao for a client and number os session as in registosSRS, to add the registosSRS id.
mongoClient.collection('sessao', function(err,collection){
collection.update(
{cliente:result.cliente_id, numero:dadosSRS.sessao},
{$set: {'dadosSRS':dadosSessao.dadosSRS}},
function(result){
if (err) throw err;
console.log(result);
console.log('encontrou registo do cliente na collection registoSRS: ' + result);
});
but the result is null, although I have the client and the session number. What am I doing wrong?
In your callback you have only one argument which is essentially the error object hence the result is null since there is no error thrown from the update option. You need a second argument which returns the actual result object. You are using the wrong update method which returns the result object if the command was executed successfully, not the actual document.
From the documentation, the result object has the following properties:
Name Type Description
result Object The raw result returned from MongoDB, field will vary depending on server version.
Properties
Name Type Description
ok Number Is 1 if the command executed correctly.
n Number The total count of documents scanned.
nModified Number The total count of documents modified.
connection Object The connection object used for the operation.
matchedCount Number The number of documents that matched the filter.
modifiedCount Number The number of documents that were modified.
upsertedCount Number The number of documents upserted.
upsertedId Object The upserted id.
You instead need to use findOneAndUpdate which will return the updated document if found
const { ObjectId } = require('mongodb'); // or ObjectID
// or var ObjectId = require('mongodb').ObjectId if node version < 6
const safeObjectId = s => ObjectId.isValid(s) ? new ObjectId(s) : null;
collection.findOneAndUpdate(
{ 'cliente': safeObjectId(result.cliente_id), 'numero': dadosSRS.sessao },
{ '$set': { 'dadosSRS': dadosSessao.dadosSRS } },
{ 'returnOriginal': true },
function(err, result) { // <-- add result as a second argument in the callback
if (err) throw err;
console.log(result);
console.log('encontrou registo do cliente na collection registoSRS: ' + result);
});

How to update a field using its previous value in MongoDB/Mongoose

For example, I have some documents that look like this:
{
id: 1
name: "foo"
}
And I want to append another string to the current name field value.
I tried the following using Mongoose, but it didn't work:
Model.findOneAndUpdate({ id: 1 }, { $set: { name: +"bar" } }, ...);
Edit:
From Compatibility Changes in MongoDB 3.6:
MongoDB 3.6.1 deprecates the snapshot query option.
For MMAPv1, use hint() on the { _id: 1} index instead to prevent a cursor from returning a document more than once if an intervening write operation results in a move of the document.
For other storage engines, use hint() with { $natural : 1 } instead.
Original 2017 answer:
You can't refer to the values of the document you want to update, so you will need one query to retrieve the document and another one to update it. It looks like there's a feature request for that in OPEN state since 2016.
If you have a collection with documents that look like:
{ "_id" : ObjectId("590a4aa8ff1809c94801ecd0"), "name" : "bar" }
Using the MongoDB shell, you can do something like this:
db.test.find({ name: "bar" }).snapshot().forEach((doc) => {
doc.name = "foo-" + doc.name;
db.test.save(doc);
});
The document will be updated as expected:
{ "_id" : ObjectId("590a4aa8ff1809c94801ecd0"), "name": "foo-bar" }
Note the .snapshot() call.
This ensures that the query will not return a document multiple times because an intervening write operation moves it due to the growth in document size.
Applying this to your Mongoose example, as explained in this official example:
Cat.findById(1, (err, cat) => {
if (err) return handleError(err);
cat.name = cat.name + "bar";
cat.save((err, updatedCat) => {
if (err) return handleError(err);
...
});
});
It's worth mentioning that there's a $concat operator in the aggregation framework, but unfortunately you can't use that in an update query.
Anyway, depending on what you need to do, you can use that together with the $out operator to save the results of the aggregation to a new collection.
With that same example, you will do:
db.test.aggregate([{
$match: { name: "bar" }
}, {
$project: { name: { $concat: ["foo", "-", "$name"] }}
}, {
$out: "prefixedTest"
}]);
And a new collection prefixedTest will be created with documents that look like:
{ "_id" : ObjectId("XXX"), "name": "foo-bar" }
Just as a reference, there's another interesting question about this same topic with a few answers worth reading: Update MongoDB field using value of another field
If this is still relevant, I have a solution for MongoDB 4.2.
I had the same problem where "projectDeadline" fields of my "project" documents were Array type (["2020","12","1"])
Using Robo3T, I connected to my MongoDB Atlas DB using SRV link. Then executed the following code and it worked for me.
Initial document:
{
_id : 'kjnolqnw.KANSasdasd',
someKey : 'someValue',
projectDeadline : ['2020','12','1']
}
CLI Command:
db
.getCollection('mainData')
.find({projectDeadline: {$not: {$eq: "noDeadline"}}})
.forEach((doc) => {
var deadline = doc.projectDeadline;
var deadlineDate = new Date(deadline);
db
.mainData
.updateOne({
_id: doc._id},
{"$set":
{"projectDeadline": deadlineDate}
}
)}
);
Resulting document:
{
_id : 'kjnolqnw.KANSasdasd',
someKey : 'someValue',
projectDeadline : '2020-12-01 21:00:00.000Z'
}

db.collection.find() is ignoring query criteria

I have the following node.js code interacting with mongo:
var lowrange = 1;
var collection = db.get('postings');
collection.find({},{postid: { $gt: lowrange }, limit: 10, sort: {_id: -1}},function(e,docs){
res.json(docs);
});
I am using this to list the contents of the collection 'postings' via a json response. The limit: 10 and sort criteria work as expected. The postid: { $gt: lowrange } section seems to be ignored.
In other words, I am getting all records, even those that are less than the var lowrange. Why is this?
Edit:
Then what is the correct syntax? This produces no results (I assure you there are documents with a post id greater than 1):
collection.find({postid: { $gt: 1 }}, function(e,docs){
res.json(docs);
});
and how do you use limit/sort when you have a function as a param of find? The following errors:
collection.find({postid: { $gt: 1 }}, function(e,docs){
res.json(docs);
}).limit(10).sort( {_id: -1});
The first parameter of find is the query document. Yours is empty ({}).
In fact, I'm not sure where you've seen that syntax, with limit and sort as field names.
See Read Operations Overview.

Multiple updates in one document using mongoose

I want to update multiple values in a single document in one mongoose call. Is this possible?
I have something similar to this:
update = {$inc : { numShown : 1 }, $inc : { secondField.subField : 1 }};
options = {};
MyModel.findByIdAndUpdate(req._id, update, options, function(err){
if(err){ return console.error(err);}
}
It runs, but doesn't update anything.
You need to combine the two $inc values into a single object and quote the dotted key:
update = { $inc : { numShown : 1, 'secondField.subField' : 1 } };

Resources