Pagination in Mongodb Nodejs - node.js

is there any method to get the total count of document in the find operation along with the skip and limit in the query in MongoDB
MongoClient.connect(Config.dbURI, function (err, db) {
if (!err) {
console.log("We are connected");
console.log(uniqueId)
db.collection(dbName).find({'uniqueId': uniqueId, 'isDeleted': false})
.sort({modifiedDateISO: -1})
.limit(parseInt(limit))
.skip(parseInt(limit * page))
.toArray((errFindChat, dataFindChat) => {
console.log(errFindChat, dataFindChat);
});
});

I assume "uniqueId" is not the primary key!
MongoClient.connect(Config.dbURI, function (err, db) {
if (!err) {
console.log("We are connected");
console.log(uniqueId)
db.collection("collname").aggregate(
[
{ "$match": { "uniqueId": uniqueId, 'isDeleted': false} },
{ "$count": "total" },
{ "$sort" : {"modifiedDateISO": -1 },
{ "$limit": parseInt(limit) },
{ "$skip" : parseInt(limit * page) }
]
).toArray((errFindChat, dataFindChat) => {
console.log(errFindChat, dataFindChat);
});
}
});
MongoDB Aggregate Count

You can't filter with skip and limit and have the total count in only one request if you use .find..
If you want to retrieve documents, filter, and perform count operation in only one request you have to use aggregate
db.coll.aggregate([
{$match: your find conditions},
{$group/project: your count operations, etc....},
{$skip: skip}, // pagination skip
{$limit: limit}, // pagination limit
...
]);

var query = {}; // Your condition
var options = {
select: 'title date author',
sort: { date: -1 },
populate: 'author',
lean: true,
offset: 20,
limit: 10
};
Book.paginate(query, options).then(function(result) {
// ...
});
The mongoose-pagination is good

Related

MongoDB projection parameter not working in findOne()

I'm trying to use a projection parameter on findOne() to extract a single field from a document (stats) but it just seems to return the whole document. I'm using version "mongodb": "^3.4.1" in Node.js
This is the document structure
{ _id: 5e563015fa9a1a0134cac3cb,
username: 'user1',
password: '1234',
email: 'user#email.com',
stats:
{ totalViewed: 122,
totalUnique: 4,
tknow: 80,
tdknow: 42,
setCnt: 78 },
progress:
[ { cardId: 1001, knowCnt: 3, dknowCnt: 4 },
{ cardId: 1016, knowCnt: 0, dknowCnt: 0 } ] }
This is the code:
var findOneDoc = function() {
db.collection("testusers").findOne(
{ username: "user1" },
{ stats: 1 }, //field to return
function(err, result) {
if (err) {
console.log("Error: ", err);
}
console.log("Success: ", result);
}
);
};
findOneDoc();
I also tried:{$project: {stats: 1}}, to no avail
Thanks
Based on the documentation the .findOne() method takes options as a second parameter and it is recommended to use projection to define fields:
db.collection("testusers").findOne(
{ username: "user1" },
{ projection: { stats: 1 } },
function(err, result) { ... }
);
with findOne operations, the second parameter passed is an options parameter, so you should pass in your projects within the options parameter, like this:
query = { username: "user1" };
options = { projection: { stats: 1 }};
db.collection("testusers").findOne(query, options)
you can do it like mick said, with findOne
await db.collection("testusers").findOne(
{ username: "user1" },
{ projection: { stats: 1 } },
function(err, result) { ... }
);
or you can do it with find, like that
await db.collection("testusers")
.find({ username: "user1" })
.limit(1)
.project(["stats"])
.toArray()
you can add fields to the array project for more fields
findOne returns a document. use find() …..

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);
}
});

$ projection in mongoDB findOneAndUpdate()

I'm trying to build a simple task queue with express and mongoose. The idea is acquire a single client and return campaign id and client id (which is a subdocument of campaign). Each time someone acquires a client, its status code is set to 1. I've come up with the following query:
router.post('/lease', (err, res) => {
Campaign.findOneAndUpdate({'isEnabled': true, 'clients.contact_status_code': 0}, {
'$set': { 'clients.$.contact_status_code': 1 },
},
{
new: true,
projection: {
'clients.$': true,
},
},
(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
});
But all i'm getting after connecting to this endpoint is this:
{"_id":"591483241a84946a79626aef","clients":[{},{}]}
It seems to me that the problem is with the $ projection, but I have no idea how to fix this.
EDIT: I tried using the following code, utilizing $elemMatch:
router.post('/lease', (err, res) => {
Campaign.findOneAndUpdate({'isEnabled': true, 'clients.contact_status_code': 0}, {
'$set': { 'clients.$.contact_status_code': 1 },
},
{
new: true,
projection: {
clients: {
'$elemMatch': {contact_status_code: 1},
}
},
},
(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
});
Unfortunately, each request yields the first subdocument in the collection, that matched the criteria -- not specifically the one that was updated. Here is an example:
Say, i have the following document in mongo:
{
"_id" : ObjectId("591493d95d48e2738b0d4317"),
"name" : "asd",
"template" : "{{displayname}}",
"isEnabled" : true,
"clients" : [
{
"displayname" : "test",
"_id" : ObjectId("591493d95d48e2738b0d4319"),
"contact_status_code" : 0
},
{
"displayname" : "client",
"_id" : ObjectId("591493d95d48e2738b0d4318"),
"contact_status_code" : 0
}
],
"__v" : 0
}
I run the query for the first time and get the following result:
{"_id":"591493d95d48e2738b0d4317","clients":[{"displayname":"test","_id":"591493d95d48e2738b0d4319","contact_status_code":1}]}
Notice client id "591493d95d48e2738b0d4319" -- this time it runs as expected. But when i run the same query the second time, I get absolutely the same object, although I expect to get one with id "591493d95d48e2738b0d4318".
The issue was with new: true
Here is a working example:
Campaign.findOneAndUpdate({'isEnabled': true, 'clients.contact_status_code': 0}, {
'$set': { 'clients.$.contact_status_code': 1 },
},
{
//new: true <-- this was causing the trouble
projection: {
clients: {
'$elemMatch': {contact_status_code: 0}, // 0 because the old record gets matched
},
},
},
(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
I assume, when the new:true is set, mongo loses the matching context. This approach returns the old record, unfortunately, but that still serves my needs to get the _id.
Seems to me you are getting the campaign id, but you also want clients.$, have you tried clients.$._id?
Update for node MongoDB 3.6 driver
unlike findAndModify the function findOneAndUpdate doesn't have the option new in its options list
But you can use instead returnDocument
returnDocument accept before OR after
When set to after,returns the updated document rather than the original
When set to before,returns the original document rather than the updated.
The default is before.
returnOriginal is Deprecated Use returnDocument instead
Here is a working example:
const filter={'isEnabled': true, 'clients.contact_status_code': 0}
const update={'$set': { 'clients.$.contact_status_code': 1 }}
const options={
returnDocument: 'after' //returns the updated document
projection: {
clients: {
'$elemMatch': {contact_status_code: 0},
// 0 because the old record gets matched
},
},
}
Campaign.findOneAndUpdate(filter,update, options
,(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
findOneAndUpdate Node.js MongoDB Driver API 3.6 documentation

Mongoose update with limit

I am looking to update X documents all at once. The short is I basically need to randomly select N documents and then update them as "selected". I'm trying to design an API that needs to randomly distribute questions. I can not find a way to do this in mongoose I have tried:
update ends up selecting everything
Question
.update({}, {
$inc: {
answerCount: 1,
lockedCount: 1
},
$push:{
devices: deviceID
}
}, {multi:true})
.limit(4)
--- I also tried
Question
.find()
.sort({
answerCount: 1,
lockedCount: 1
})
.limit(req.query.limit || 4)
.update({}, {
$inc: {
answerCount: 1,
lockedCount: 1
},
$push:{
devices: deviceID
}
}, { multi: true }, callback);
Both resulted in updating all docs. Is there a way to push this down to mongoose without having to use map ? The other thing I did not mention is .update() without multi resulted in 1 document being updated.
You could also pull an array of _ids that you'd like to update then run the update query using $in. This will require two calls to the mongo however the updates will still be atomic:
Question.find().select("_id").limit(4).exec(function(err, questions) {
var q = Question.update({_id: {$in: questions}}, {
$inc: {answerCount: 1, lockedCount:1},
$push: {devices: deviceid}
}, {multi:true});
q.exec(function(err) {
console.log("Done");
});
});
So I did an simple map implementation and will use it unless someone can find a more efficient way to do it.
Question
.find({
devices: { $ne: deviceID}
},
{ name: true, _id: true})
.sort({
answerCount: 1,
lockedCount: 1
})
.limit(req.query.limit || 4)
.exec(updateAllFound );
function updateAllFound(err, questions) {
if (err) {
return handleError(res, err);
}
var ids = questions.map(function(item){
return item._id;
});
return Question.update({ _id: { $in: ids} } ,
{
$inc: {
answerCount: 1,
lockedCount: 1
},
$push:{
devices: deviceID
}
}, { multi: true }, getByDeviceID);
function getByDeviceID(){
return res.json(200, questions);
}
}

How to use populate functionality by using populate or making inner query with aggregation in mongodb

I have following data in my Mongodb.
{
"_id" : ObjectId("54a0d4c5bffabd6a179834eb"),
"is_afternoon_scheduled" : true,
"employee_id" : ObjectId("546f0a06c7555ae310ae925a")
}
I would like to use populate with aggregate, and want to fetch employee complete information in the same response, I need help in this. My code is:
var mongoose = require("mongoose");
var empid = mongoose.Types.ObjectId("54a0d4c5bffabd6a179834eb");
Availability.aggregate()
.match( { employee_id : empid} )
.group({_id : "$employee_id",count: { $sum: 1 }})
.exec(function (err, response) {
if (err) console.log(err);
res.json({"message": "success", "data": response, "status_code": "200"});
}
);
The response i am getting is
{"message":"success","data":{"_id":"54a0d4c5bffabd6a179834eb","count":1},"status_code":"200"}
My expected response is:
{"message":"success","data":[{"_id":"54aa34fb09dc5a54232e44b0","count":1, "employee":{fname:abc,lname:abcl}}],"status_code":"200"}
You can call the model form of .populate() on the result objects from an aggregate operation. But the thing is you are going to need a model to represent the "Result" object returned by your aggregation in order to do so.
There are a couple of steps, best explained with a complete listing:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var employeeSchema = new Schema({
"fname": String,
"lname": String
})
var availSchema = new Schema({
"is_afternoon_scheduled": Boolean,
"employee_id": {
"type": Schema.Types.ObjectId,
"ref": "Employee"
}
});
var resultSchema = new Schema({
"_id": {
"type": Schema.Types.ObjectId,
"ref": "Employee"
},
"count": Number
});
var Employee = mongoose.model( "Employee", employeeSchema );
var Availability = mongoose.model( "Availability", availSchema );
var Result = mongoose.model( "Result", resultSchema, null );
mongoose.connect('mongodb://localhost/aggtest');
async.series(
[
function(callback) {
async.each([Employee,Availability],function(model,callback) {
model.remove({},function(err,count) {
console.log( count );
callback(err);
});
},callback);
},
function(callback) {
async.waterfall(
[
function(callback) {
var employee = new Employee({
"fname": "abc",
"lname": "xyz"
});
employee.save(function(err,employee) {
console.log(employee),
callback(err,employee);
});
},
function(employee,callback) {
var avail = new Availability({
"is_afternoon_scheduled": true,
"employee_id": employee
});
avail.save(function(err,avail) {
console.log(avail);
callback(err);
});
}
],
callback
);
},
function(callback) {
Availability.aggregate(
[
{ "$group": {
"_id": "$employee_id",
"count": { "$sum": 1 }
}}
],
function(err,results) {
results = results.map(function(result) {
return new Result( result );
});
Employee.populate(results,{ "path": "_id" },function(err,results) {
console.log(results);
callback(err);
});
}
);
}
],
function(err,result) {
if (err) throw err;
mongoose.disconnect();
}
);
That's the complete example, but taking a closer look at what happens inside the aggregate result is the main point:
function(err,results) {
results = results.map(function(result) {
return new Result( result );
});
Employee.populate(results,{ "path": "_id" },function(err,results) {
console.log(results);
callback(err);
});
}
The first thing to be aware of is that the results returned by .aggregate() are not mongoose documents as they would be in a .find() query. This is because aggregation pipelines typically alter the document in results from what the original schema looked like. Since it is just a raw object, each element is re-cast as a mongoose document for the Result model type defined earlier.
Now in order to .populate() with data from Employee, the model form of this method is called on the array of results in document object form along with the "path" argument to the field to be populated.
The end result fills is the data as it comes from the Employee model it was related to.
[ { _id:
{ _id: 54ab2e3328f21063640cf446,
fname: 'abc',
lname: 'xyz',
__v: 0 },
count: 1 } ]
Different to how you process with find, but it is necessary to "re-cast" and manually call in this way due to how the results are returned.
This is working like applied populate with aggregate using inner query.
var mongoose = require("mongoose");
var empid = mongoose.Types.ObjectId("54a0d4c5bffabd6a179834eb");
Availability.aggregate()
.match( { employee_id : empid} )
.group({_id : "$employee_id",count: { $sum: 1 }})
.exec(function (err, response) {
if (err) console.log(err);
if (response.length) {
var x = 0;
for (var i=0; i< response.length; i++) {
empID = response[i]._id;
if (x === response.length -1 ) {
User.find({_id: empID}, function(err, users){
res.json({"message": "success", "data": users, "status_code": "200"});
});
}
x++;
}
}
}
);

Resources