How to use $match in aggregate in mongodb for optional parameter - node.js

I m new in mongodb and stucked while using aggregate function.
my query is -> I want to filter database record based on my applied filter in dashboard.
How can i add filter value using $match operator? it should apply when filter value is present and ignore if not avaliable.
if (req.body.filterSet !== undefined) {
const filterData = req.body.filterSet[0];
var violation_id = filterData.violation_id;
var start_notice = filterData.start_notice;
var end_notice = filterData.end_notice;
var rc_number = filterData.rc_number;
var circle = filterData.circle;
var start_date = filterData.start_date;
var end_date = filterData.end_date;
var status = filterData.status;
var source = filterData.source;
var sms_status = filterData.sms_status;
var notice_status = filterData.notice_status;
}
Complaint.aggregate([
{ $match : { is_active : { $eq : 1 } } },
{ $match : { id : { $eq : 1103186 } } },
{ $lookup:{ from: 'offences', localField:'offences', foreignField: 'offence_id', as: 'offenceSetail' } },
{ $project: { 'offences.is_active' : 0 } },
{ $replaceRoot : { newRoot : { $mergeObjects : [ { $arrayElemAt: [ "$offenceSetail", 0] }, "$$ROOT"] } } },
{ $project: { 'offenceSetail' : 0 } },
{ $lookup: { from: 'registers', localField: 'user_id', foreignField: 'id', as: 'sender'} },
{ $project: { 'registers.is_active' : 0 } },
{ $replaceRoot : { newRoot : { $mergeObjects : [ { $arrayElemAt: ['$sender', 0] }, "$$ROOT"] } } },
{ $project: { 'sender': 0 } },
{ $facet : { length : [ { $count : "total" }], data : [ { $skip: skip }, {$limit: limit } ] } },
])

you can construct your aggregation stages dynamically like this:
const stages = [
{ $lookup:{ from: 'offences', localField:'offences', foreignField: 'offence_id', as: 'offenceSetail' } },
{ $project: { 'offences.is_active' : 0 } },
{ $replaceRoot : { newRoot : { $mergeObjects : [ { $arrayElemAt: [ "$offenceSetail", 0] }, "$$ROOT"] } } },
{ $project: { 'offenceSetail' : 0 } },
{ $lookup: { from: 'registers', localField: 'user_id', foreignField: 'id', as: 'sender'} },
{ $project: { 'registers.is_active' : 0 } },
{ $replaceRoot : { newRoot : { $mergeObjects : [ { $arrayElemAt: ['$sender', 0] }, "$$ROOT"] } } },
{ $project: { 'sender': 0 } },
{ $facet : { length : [ { $count : "total" }], data : [ { $skip: skip }, {$limit: limit } ] } }
]
if (optional_parameter_exists) {
stages.unshift(
{ $match: { is_active: { $eq: 1 } } },
{ $match: { id: { $eq: 1103186 } } }
);
}
Complaint.aggregate(stages);

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

Return all results in aggregate if match query parameter is null

My Aggregate query:
const categoryId = req.query.categoryId
const results = await Question.aggregate([
{
$match:{
$and : [
{ category : mongoose.Types.ObjectId(categoryId) },
{ category : {$ne : null} }
]
}
},{
$lookup: {
from: "answer",
let: { questionId: "$_id" },
pipeline: [{ $match: { $expr: { $eq: ["$$questionId", "$questionId"] } } }],
as: "answerCount"
}
},{ $addFields: { answerCount: { $size: "$answerCount" }}}, {
$lookup: {
from: "users",
let : {id : "$creator"},
as : "creator",
pipeline : [
{$match : {$expr : {$eq: ["$$id","$_id"]}}},
{$project : {name : 1, profilePhoto : 1}}
]
}
}, {$unwind: "$creator"},{
$lookup: {
from: "categories",
let : { id: "$category" },
as : "category",
pipeline: [
{ $match : { $expr: { $eq: ["$_id", "$$id"] } }},
{ $project: { name: 1}}
]
}
}, {$unwind : "$category"},{
$unset: ["createdAt", "updatedAt", "__v"]
}
])
Now using $match query I fetch only the Questions belonging to a specific category. What I want to do is if the categoryId is null, it should return all the results. Right now it returns an empty array. How do i go about doing that?
Try This:
const categoryId = req.query.categoryId
let conditions = {
// You can also have some default condition that always results true
};
if (categoryId) {
conditions = {
"category": mongoose.Types.ObjectId(categoryId)
// More conditions in future...
}
}
const results = await Question.aggregate([
{
$match: conditions
},
{
$lookup: {
from: "answer",
let: { questionId: "$_id" },
pipeline: [{ $match: { $expr: { $eq: ["$$questionId", "$questionId"] } } }],
as: "answerCount"
}
},
{ $addFields: { answerCount: { $size: "$answerCount" } } },
{
$lookup: {
from: "users",
let: { id: "$creator" },
as: "creator",
pipeline: [
{ $match: { $expr: { $eq: ["$$id", "$_id"] } } },
{ $project: { name: 1, profilePhoto: 1 } }
]
}
},
{ $unwind: "$creator" },
{
$lookup: {
from: "categories",
let: { id: "$category" },
as: "category",
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$id"] } } },
{ $project: { name: 1 } }
]
}
},
{ $unwind: "$category" },
{
$unset: ["createdAt", "updatedAt", "__v"]
}
]);
also read about preserveNullAndEmptyArrays property in $unwind operator if required.
https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

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

mongodb update query in aggregation from multiple collections without grouping

can i update specific fields in a particular collection according to the matched keys from different collections i.e suppose i have 3 collections
**///collection 1: col1///**
_id:ObjectId("#####7b")
name:'name1',
itemsBought:
[
{
"_id":ObjectId("####c1"
"itemName" : "name1",
"itemCode" : "IT001",
"itemQuantity" : 19,
"itemPrediction":23
},
{
"_id":ObjectId("####c2"
"itemName" : "name2",
"itemCode" : "IT002",
"itemQuantity" : 79,
"itemPrediction":69
},
{
"_id":ObjectId("####c3"
"itemName" : "name2",
"itemCode" : "IT003",
"itemQuantity" : 0,
"itemPrediction":0
},
]
**///collection 1: col2///**
{
"itemQuantity" : 21,
"itemCode" : "IT001",
},
{
"itemQuantity" : 2,
"itemCode" : "IT003",
}
**///collection 1: col3///**
{
"itemCode" : "IT001",
"itemPrediction":23
},
{
"itemCode" : "IT002",
"itemPrediction":12
},
{
"itemCode" : "IT003",
"itemPrediction":7
},
i am using $aggregation $lookup to fetch out all the required data, before sending it to the frontend i need to fetch the values of itemQuantity from col2 and itemPrediction from col3 and update that in col1 with the matching itemCode. So i have the query which fetches out all the data from all the collections but i dont know how to use $set to update the values in col1.
Workaround: You may perform aggregation and save the result manually
db.col1.aggregate([
{
$lookup: {
from: "col2",
let: {
root_itemCode: "$itemsBought.itemCode"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$itemCode",
"$$root_itemCode"
]
}
}
}
],
as: "col2"
}
},
{
$lookup: {
from: "col3",
let: {
root_itemCode: "$itemsBought.itemCode"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$itemCode",
"$$root_itemCode"
]
}
}
}
],
as: "col3"
}
},
{
$addFields: {
itemsBought: {
$map: {
input: "$itemsBought",
as: "item",
in: {
"_id": "$$item._id",
"itemName": "$$item.itemName",
"itemCode": "$$item.itemCode",
"itemQuantity": {
$let: {
vars: {
input: {
$arrayElemAt: [
{
$filter: {
input: "$col2",
cond: {
$eq: [
"$$item.itemCode",
"$$this.itemCode"
]
}
}
},
0
]
},
default: "$$item.itemQuantity"
},
in: {
$ifNull: [
"$$input.itemQuantity",
"$$default"
]
}
}
},
"itemPrediction": {
$let: {
vars: {
input: {
$arrayElemAt: [
{
$filter: {
input: "$col3",
cond: {
$eq: [
"$$item.itemCode",
"$$this.itemCode"
]
}
}
},
0
]
},
default: "$$item.itemPrediction"
},
in: {
$ifNull: [
"$$input.itemPrediction",
"$$default"
]
}
}
}
}
}
}
}
},
{
$unset: [
"col2",
"col3"
]
}
])
MongoPlayground
Mongoose
Collection1.aggregate([...], function (err, result) {
if(err) console.log("error-agg: " + err);
result.forEach(function(item) {
Collection1.updateOne({_id:item._id}, {$set:item}, function (err) {
if(err) console.log("error-saving: " + err);
});
});
});

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