Count Documents in mongoose in single query - node.js

const postsCount = await Post.find(query).countDocuments();
const allPosts = await Post.find(query)
.populate('likes')
.skip(startIndex)
.limit(noOfRecordsPerPage)
.sort({ createdAt: 'desc' });
I am querying two times in Database to find the total document count . How can i do this with a single query to the database ?

//you can use aggregation pipeline and $facet to combine two DB requests into one
//Actual Code output from windows Mongoshell/Command Line Interface(CLI)
> print("mongoDB",db.version())
mongoDB 4.2.6
> db.groups1.aggregate([
... {$facet:{
... "docCount":[
... {$group:{
... _id:null,
... count:{$sum:1}
... }
... }
... ],
... "allDocs":[
... {$match:{}}
... ]
... }
... }
... ]).pretty();
{
"docCount" : [
{
"_id" : null,
"count" : 4
}
],
"allDocs" : [
{
"_id" : ObjectId("5f817d142689512ef0bd3bd7"),
"uid" : 1,
"group_id" : "g1"
},
{
"_id" : ObjectId("5f817d142689512ef0bd3bd8"),
"uid" : 1,
"group_id" : "g2"
},
{
"_id" : ObjectId("5f817d142689512ef0bd3bd9"),
"uid" : 2,
"group_id" : "g2"
},
{
"_id" : ObjectId("5f817d142689512ef0bd3bd1"),
"uid" : 2,
"group_id" : "g3"
}
]
}
>

Use the $facet aggregation stage to combine multiple nested pipelines into your master pipeline.
Here's a working code of the above idea -
const countPipeline = [
{
$match: query
},
{
$group: {
_id: null,
count: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
count: '$count'
}
}
];
const postPipeline = [
{
$match: query
},
{
$skip: startIndex
},
{
$limit: noOfRecordsPerPage
},
{
$sort: {
createdAt: -1
}
}
];
const pipeline = [
{
$facet: {
posts: postPipeline,
count: countPipeline
}
}
];
const results = await Posts.aggreagate(pipeline).exec();

Related

Facing problems to retrieve data using mongodb Pipeline method using Nodejs

I am facing some problems to crack this particular challenge where I would like to retrieve data from MongoDb using pipeline in Nodejs.
Here is the data from DB:
{
"_id" : ObjectId("623ec44a6900ca5e3a88ece0"),
"student_id" : ObjectId("5fca683c239e4e2693ee20e4"),
"isSession" : true,
"record_list" : [
{
"_id" : ObjectId("623ec4ae319ebd3d243adeb4"),
"Name" : "Adam",
"Type" : 1,
"class_id" : "aa0a2311-5989-4a7b-855f-4c4b73ae6315",
"isPresent" : true
},
{
"_id" : ObjectId("623ec4ae319ebd3d243adeb5"),
"Name" : "Jack",
"Type" : 1,
"class_id" : "fa54389b-4504-465b-9c6c-a386918b8d67",
"isPresent" : true
}
]
}
Here is the output that I expect:
{
"_id" : ObjectId("623ec44a6900ca5e3a88ece0"),
"student_id" : ObjectId("5fca683c239e4e2693ee20e4"),
"isSession" : true,
"record_list" : [
{
"_id" : ObjectId("623ec4ae319ebd3d243adeb5"),
"Name" : "Jack",
"Type" : 1,
"class_id" : "fa54389b-4504-465b-9c6c-a386918b8d67",
"isPresent" : true
}
]
}
I am sending class_id and student_id from UI and based on that it should filter the data and return the entire collection based on these parameters.
controller.ts
let pipeline: any = await Builder.User.SchoolBuilder.studentMatchRecord(payload);
What I tried:
school.builder.ts
export const studentMatchRecord = (payload: any) => {
let pipeline: any = [];
pipeline.push({ $match: { student_id: Types.ObjectId(payload.studentId) } });
pipeline.push(
{ "$unwind": "$record_list" },
{
"$group": {
"record_list.class_id": payload.classId,
}
});
pipeline.push({
$project: {
student_id: 1,
isSession: 1,
record_list: 1,
},
});
return pipeline;
};
and even this:
export const studentMatchRecord = (payload: any) => {
let pipeline: any = [];
let filterConditions: any = { $match: { $and: [] } };
let matchCriteria: any = { $match: { $and: [] } };
matchCriteria.$match.$and.push({
student_id: Types.ObjectId(payload.student_id),
});
if (payload.classId) {
filterConditions.$match.$and.push({
"record_list.class_id": payload.class_id,
});
}
if (filterConditions.$match.$and.length > 0) {
pipeline.push(filterConditions);
}
pipeline.push({
$project: {
student_id: 1,
isSession: 1,
record_list: 1,
},
});
return pipeline;
};
but none of the case is working here. Can you please highlight what am I doing wrong here? Cheers.
Option 1: find/$elemMatch:
db.collection.find({
"student_id": ObjectId("5fca683c239e4e2693ee20e4")
},
{
"record_list": {
"$elemMatch": {
"class_id": "aa0a2311-5989-4a7b-855f-4c4b73ae6315"
}
},
"isSession": 1,
"student_id": 1
})
playground1
Option 2: aggregate/$filter
db.collection.aggregate([
{
$match: {
"student_id": ObjectId("5fca683c239e4e2693ee20e4")
}
},
{
"$addFields": {
"record_list": {
"$filter": {
"input": "$record_list",
"as": "rec",
"cond": {
$eq: [
"$$rec.class_id",
"fa54389b-4504-465b-9c6c-a386918b8d67"
]
}
}
}
}
}
])
playground2

Get Total Count using aggregate + facet using Mongo

I want to group my data based on event date with pagination. However what i am getting is whole record totalcount instead of eventDate count. because of this UI part is not working properly. Here is my collection sample:
{
"_id" : ObjectId("5fc4d0009a25e8cfbe306381"),
"eventDate" : ISODate("2021-11-29T01:00:00.000Z"),
"team1" : {
"tName" : "Chicago Bears",
},
"team2" : {
"tName" : "Green Bay Packers",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063ff"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "Nashville SC",
},
"team2" : {
"tName" : "Columbus Crew",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063f4"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "yyyy",
},
"team2" : {
"tName" : "xxxx",
}
}
here is my query:
db.getCollection('game').aggregate([
{ $addFields: { "newEventDate": {$dateToString:{ format: "%Y-%m-%d", date: "$eventDate" }}}},
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$facet": {
"resultData": [
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$group": {
"_id": "$newEventDate",
"games": {$push: {
team1:"$team1",
team2:"$team2"
}}
}
},
{ $sort: {eventDate: 1} },
{
$limit: 1
}
],
"pageInfo": [
{ "$count": "totalRecords" }
]}
}
]);
After executing this query this is my response:
{
"resultData" : [
{
"_id" : "2021-11-29",
"games" : [
{
"awayTeam" : {
"tName" : "Chicago Bears"
},
"homeTeam" : {
"tName" : "Green Bay Packers"
}
},
]
}
],
"pageInfo" : [
{
"totalRecords" : 3 **[here i want 2 ie total event date]**
}
]
}
$match your condition
move your $group stage outside from $facet, convert your date from string inside group, add you date in group stage because we are going to sort in next stage
$sort by eventDate ascending order
$facet, first get single record using $limit, and second part get total count of the record using $count
db.collection.aggregate([
{ $match: { eventDate: { $gte: new Date() } } },
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$eventDate"
}
},
eventDate: { $first: "$eventDate" },
games: {
$push: {
team1: "$team1",
team2: "$team2"
}
}
}
},
{ $sort: { eventDate: 1 } },
{
$facet: {
resultData: [{ $limit: 1 }],
pageInfo: [{ $count: "totalRecords" }]
}
}
])
Playground

wrong counting with $filter

My db cofiguration looks like:
{
"_id" : ObjectId("5ece47aa6510a611b47aac5a"),
"boats" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac6e"),
"model" : "Dufour",
"year" : 2019,
"about" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 14,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 200
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 120
},
]
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 8,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "benzin",
"fuelCap" : 180
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 100
},
]
},
{...},
{...},
]
}
Now i am trying to count the number of boats which have "fuel" : "petrol", so i use the code bellow:
router.get('/boat', async(req, res)=>{
try{
const fuelData = await Boat.aggregate([
{
$project: {
fuelData: {
$filter: {
input: "$boats",
as: "boats",
cond: {
$filter:{
input:"$$boats.about",
as:"about",
cond:{
$filter:{
input:"$$about.characteristics",
as:"characteristics",
cond:{
$eq:["$$activity1.activity.type", "STILL"]
}
}
}
}
}
}
}
}
},
{
$project: {
boatsCount: {$size : "$fuelData" }
}
}
])
res.status(201).send(fuelData)
}catch(e){
res.send(e)
}
})
The problem is that return wrong number of boatCount. And it seems like it returns the number of the boats which are inside the db. Any help how to count correctly the boats which have "fuel" : "petrol"?
Is there anything wrong in my code?
https://mongoplayground.net/p/D0FEhTMJEJ1
Hope this is what you need.
The sample data you have provided is missing a ] & }.
So I added 1 additional boat with 2 about.
db.collection.aggregate([
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$unwind: "$boats"
},
{
$unwind: "$boats.about"
},
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
])

How to get group with groupby get the result with dynamic status in mongoDB

I have the collection like below
{
"_id" : ObjectId("5b6538704ba0292b6c197770"),
"Name":"Name1",
"Status":"Good",
},
{
"_id" : ObjectId("5b6538704ba0292b6c197773"),
"Name":"Name2",
"Status":"Bad"
},
{
"_id" : ObjectId("5b6538704ba0292b6c197774"),
"Name":"Name3",
"Status":"Bad"
},{
"_id" : ObjectId("5b6538704ba0292b6c197774"),
"Name":"Name1",
"Status":"Bad"
},
{
"_id" : ObjectId("5b6538704ba0292b6c197775"),
"Name":"Name1",
"Status":"Good"
}
I have used the query to get the status wise count like below
db.Students.aggregate( [
{ $group: { _id: {
"Name": "$Name","Status":"$Status"}, StatusCount: { $sum: 1 } } }
, { "$project": { _id: 0, Name: "$_id.Name",Status : "$_id.Status", StatusCount:1 } }
] );
The result was
{
"Name":"Name1",
"StatusCount" : 2,
"Status" : "Good"
},
{
"Name":"Name2",
"StatusCount" : 1,
"Status" : "Bad"
}, {
"Name":"Name2",
"StatusCount" : 1,
"Status" : "Bad"
},
{
"Name":"Name1",
"StatusCount" : 1,
"Status" : "Bad"
}
The result what I am approaching is like
{
"Name":"Name1",
"Good" : 2,
"Bad" :1
},
{
"Name":"Name2",
"Good" : 0,
"Bad" :1
}
The result I am expecting to have the status of field names and count as its values. I have tried to do this but I could not make it happen. The status, for now, is only two like Good or Bad but may increase in real dataset.
By using the $arrayToObject operator and a final $replaceRoot pipeline step which has a $mergeObjects operator you will get your desired result.
You would need to run the following aggregate pipeline on MongoDB Server 3.4.4 or newer:
const pipeline = [
{ '$group': {
'_id': {
'Name': '$Name',
'Status': '$Status'
},
'StatusCount': { '$sum': 1 }
} },
{ '$group': {
'_id': '$_id.Name',
'counts': {
'$push': {
'k': '$_id.Status',
'v': '$StatusCount'
}
}
} },
{ '$replaceRoot': {
'newRoot': { '$mergeObjects': [
{ '$arrayToObject': '$counts' },
{ 'Name': '$_id' }
] }
} }
];
db.Students.aggregate(pipeline);

how to perform group by in mongodb

I'm having this data in MongoDB, I want to perform group by on this data to get all the tracking numbers, under the same id in a single array format.
* 1 */
{
"_id" : ObjectId("597056182a93692b4c7691bf"),
"Sid":1,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "121_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "779591314278"
},
{
"TrackingNo" : "779591314039"
},
{
"TrackingNo" : "779591314231"
}
]
}
]
}
/* 2 */
{
"_id" : ObjectId("597057338c65c002e4285fb3"),
"Sid":2,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "122_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "77959131427"
},
{
"TrackingNo" : "77959131403"
}
]
}
]
}
/* 3 */
{
"_id" : ObjectId("5980ae7ecc71b581b626d20b"),
"Sid":3,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "123_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "77959131408"
},
{
"TrackingNo" : "779591314059"
},
{
"TrackingNo" : "779591315551"
}
]
}
]
}
I.e on performing group by operation on _id, which shows all tracking numbers under the same _Id in a single array, means the result set will be like this :
{
"_id" : ObjectId("597056182a93692b4c7691bf"),
"Sid":1,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "121_dom"
}
{
"TrackingNo" : "779591314278"
},
{
"TrackingNo" : "779591314039"
},
{
"TrackingNo" : "779591314231"
}
]
}
]
}
As you have array within array, you first need to unwind twice and then group again. Following query should work. You need to project additional fields in $group and $project as required.
db.shipments.aggregate(
{$unwind:'$Carriers'},
{$unwind: '$Carriers.Tracking'},
{$group: {_id:'$_id', c: {$push: '$Carriers.Tracking'}}},
{$project:{Carriers: {Tracking: '$c'}}}
)
According to description as mentioned in above question please try executing following aggregate query into MongoDB shell as a solution to above mentioned question.
db.shipments.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: '$Carriers'
}
},
// Stage 2
{
$unwind: {
path: '$Carriers.Tracking'
}
},
// Stage 3
{
$group: {
_id: {
_id: '$_id',
Sid: '$Sid'
},
Carriers: {
$addToSet: {
TrackingNo: '$Carriers.Tracking.TrackingNo'
}
}
}
},
// Stage 4
{
$group: {
_id: '$_id',
Carriers: {
$addToSet: {
Tracking: '$Carriers'
}
}
}
},
// Stage 5
{
$project: {
_id: '$_id._id',
Sid: '$_id.Sid',
Carriers: '$Carriers'
}
}
]
);

Resources