How to find data with condition on a second collection with Mongoose - node.js

I have a collection "campanas" with similar structure to:
{
_id: ObjectId,
name: String,
starts: Date,
ends: Date,
available: Boolean,
max_requirements: Int,
}
Whenever a user interacts with the Campana a requirement is generated with this structure in a different collection called "campana_requrements":
{
_id: ObjectId,
id_campana: {type: ObjectId, ref: "campanas"},
id_user: {type: ObjectId, ref: "users",
action_needed: String
}
Campanas also has attributes that are stored in its own collection called "campanas_attributes"
{
_id: ObjectId,
attribute_name: String
}
and campana and attributes are related through a 3rd collection called "campana_campana_attribute"
{
_id: ObjectId,
id_campana: {type: ObjectId, ref: "campanas"},
id_campana_attribute: {type: ObjectId, ref:"campana_attributes"}
}
So campanas I need to retrieve Campanas with its attributes BUT filter out the ones which has requirements generated by the user, in pseudo code the condition would be
campanas.max_requirements < requirements.length WHERE requirements.id_campana = campana._id AND requirements.id_user = $user
where $user is a variable I would provide.
I can easily find the campanas and add its attributes, but i cant find a way to filter depending on amount of requirements using only mongodb / mongoose methods.
I want to achieve this in the query, not with nodejs.
How can i achived this?
UPDATE
I almost got it working using Studio3T aggregation tool but the reference of "$max_requirements" fails on the $match statement, its not taking the value. It works like a charm if i hard code a 1 into it but it might not be a 1 in the future.
Heres the aggregation:
// Requires official MongoShell 3.6+
campanas.aggregate(
[
{
"$lookup" : {
"from" : "tbl_campana_requirements",
"localField" : "_id",
"foreignField" : "id_campana",
"as" : "requirements",
"pipeline" : [
{
"$match" : {
"$or" : [
{
"id_user" : ObjectId("someId")
},
{
"id_user" : null
}
]
}
}
]
}
},
{
"$addFields" : {
"req_count" : {
"$size" : "$requirements"
}
}
},
{
"$match" : {
"habilitado" : true,
"$or" : [
{
"req_count" : {
"$lt" : "$max_requirements" <--this is the problem
}
},
{
"max_requirements" : {
"$eq" : 0.0
}
}
]
}
},
{
"$lookup" : {
"from" : "tbl_campana_campana_atributos",
"localField" : "_id",
"foreignField" : "id_campana",
"pipeline" : [
{
"$lookup" : {
"from" : "tbl_campana_atributos",
"localField" : "id_campana_atributo",
"foreignField" : "_id",
"as" : "atributo"
}
},
{
"$project" : {
"_id" : 0.0,
"atributo" : "$atributo"
}
},
{
"$unwind" : "$atributo"
}
],
"as" : "atributos"
}
},
{
"$project" : {
"atributos" : "$atributos.atributo",
"habilitado" : 1.0,
"nombre" : 1.0,
"titulo" : 1.0,
"descripcion" : 1.0,
"fecha_inicio" : 1.0,
"fecha_fin" : 1.0,
"id_campana_responsable" : 1.0,
"url_imagen_banner" : 1.0,
"url_imagen_fondo" : 1.0,
"url_imagen_principal" : 1.0,
"condiciones_terminos" : 1.0
}
}
],
{
"allowDiskUse" : false
}
);

Related

Update inner object of mongo model with innder id

My Model which has coure_name, c_material as array, c_task as array and I am trying to fetch the results based on the below query:
const taks_schema = mongoose.Schema({
course_name : {
type : String,
required : true,
unique : [true, "Someone register using this email"],
trim : true
},
c_material : [
{
m_name : {
type : String
},
m_link : {
type : String
},
m_file : {
type : String
},
m_date : {
type : String
}
}
],
c_task : [
{
t_name : {
type : String
},
t_details : {
type : String
},
t_start_date : {
type : Date
},
t_end_date : {
type : Date
},
t_q_file : {
type : String
},
t_s_file : [
{
t_s_email: {
type : String
},
t_s_file_inner : {
type : Array
},
t_s_file_Date : {
type : Date
}
}
]
}
]
});
I apply this query which searches through the database, but get 0 modified in the result object.
let submit_files = await stu_task.updateOne({$and: [{course_name : "Web Development with Node js"}, {c_task : {_id : req.body.task_id}}]}, {$set : {c_task : {t_s_file : {t_s_email : check_cookie.email, t_s_file_inner : files_names, t_s_file_Date : Date.now()}}}});
Is there any problem in the query that I have created?
You don't have "c_task._id" defined in the schema , but you have it in the updateOne search criteria ...
You also need arrayFilters to update nested arrays check the documentation and also you need a id to search in t_s_file array in following query is used t_s_email field because I don't know the real data, so I searching in t_s_file based on t_s_email can using _id if exist
await stu_task.updateOne(
{
$and: [
{ course_name: "Web Development with Node js" },
{ "c_task._id": req.body.task_id },
],
},
{
$set: {
"c_task.$[].t_s_file.$[i].t_s_email": {
t_s_email: check_cookie.email,
t_s_file_inner: files_names,
t_s_file_Date: Date.now(),
},
},
},
{ arrayFilters: [{ "i.t_s_email": "email#gmail.com" }] }
);

$aggregation, $sum on two collections in MongoDB

Hi suppose I have these two collections
sample1 :
{
"_id" : {
"date" : ISODate("2020-02-11T18:30:00Z"),
"price" : 4,
"offer" : 0,
"itemCode" : "A001"
"customerId" : ObjectId("5e43de778b57693cd46859eb"),
"sellerId" : ObjectId("5e43e5cdc11f750864f46820"),
},
"charges" : 168
}
{
"_id" : {
"date" : ISODate("2020-02-11T18:30:00Z"),
"coverPrice" :5.5 ,
"offer" : 38,
"itemCode" : "B001"
"customerId" : ObjectId("5e43de778b57693cd46859eb"),
"sellerId" : ObjectId("5e43e5cdc11f750864f46820"),
},
"charges" : 209.5
}
NOTE : sample1's _id doesnot have any ObjectId().
sample2 :
{
"paymentReceivedOnDate" : ISODate("2020-02-12T18:30:00Z"),
"customerId" : ObjectId("5e43de778b57693cd46859eb"),
"sellerId" : ObjectId("5e43e5cdc11f750864f46820"),
"amount" : 30,
}
{
"paymentReceivedOnDate" : ISODate("2020-02-12T18:30:00Z"),
"customerId" : ObjectId("5e43de778b57693cd46859eb"),
"sellerId" : ObjectId("5e43e5cdc11f750864f46820"),
"amount" : 160,
}
{
"paymentReceivedOnDate" : ISODate("2020-02-11T18:30:00Z"),
"customerId" : ObjectId("5e43de778b57693cd46859eb"),
"sellerId" : ObjectId("5e43e5cdc11f750864f46820"),
"amount" : 50,
}
My problem statements :
1: Firstly i need to calculate the totalCharges from sample 1 collection. against the [date,customerId,sellerId ]
2: Secondly i need to calculate totalAmount from sample 2 collection.
3: Than i need calculte the outstanding i.e [totalCharges - totalAmount].
4: lastly and most importantly i need to save the projected result into a new collection suppose "result" with the following fields-['customerId','sellerId','date','totalCharges','outstanding'(i.e: [totalCharges - totalAmount]),'totalAmount'.
You can try below query :
db.sample1.aggregate([
/** groups data & sum up charges */
{ $group: { _id: { date: '$_id.date', customerId: '$_id.customerId', sellerId: '$_id.sellerId' }, totalCharges: { $sum: '$charges' } } },
/** finds matching docs from sample2 */
{
$lookup:
{
from: "sample2",
let: { customerId: '$_id.customerId', sellerId: '$_id.sellerId' },
pipeline: [
{
$match:
{
$expr:
{
$and:
[
{ $eq: ["$customerId", "$$customerId"] },
{ $eq: ["$sellerId", "$$sellerId"] }
]
}
}
},
{ $project: { amount: 1, _id: 0 } }
],
as: "TotalAmount" // TotalAmount is an array of objects, each object will have just amount field in it.
}
},
/** retains only needed fields */
{
$project: {
totalCharges: 1, outstanding: {
$subtract: ['$totalCharges', {
$reduce: {
input: '$TotalAmount',
initialValue: 0,
in: { $add: ["$$value", "$$this.amount"] }
}
}]
}, TotalAmount: {
$reduce: {
input: '$TotalAmount',
initialValue: 0,
in: { $add: ["$$value", "$$this.amount"] }
}
}
}
}
])
Test : MongoDB-Playground
Ref : aggregation-pipeline
Note : At the end of the aggregation you can have either $merge or $out stage to write aggregation results into new collection, If your MongoDB v >=4.2 then prefer $merge cause it will merge fields to existing documents/adds new documents to existing collection or if no collection is found with given name it would create new collection, But where as $out will completely replaces existing collection if provided collection name already exists or creates new collection with provided name.

How to group over Array elements using Mongoose Aggregation FrameWork

I have following Mongoose schemas :
EmployeeSchema :
var EmployeeSchema = new Schema({
name : String,
employeeDetailsId: {
type: Schema.Types.ObjectId,
ref: 'employeedetails'
}
});
EmployeeDetailSchema :
var EmployeeDetailSchema = new Schema({
employeeId: {
type: Schema.Types.ObjectId,
ref: 'employee'
},
statusId: {
type: Schema.Types.ObjectId,
ref: 'status'
},
primarySkills: [
{
type: Schema.Types.ObjectId,
ref: 'skills'
}]
});
SkillsSchema :
var SkillsSchema = new Schema({
name: {
type: String,
required: true
}
});
Following is the data that's got saved in EmployeeDetails collection :
/* 1 */
{
"_id" : ObjectId("583fbbfe78854dd424f0523f"),
"employeeId" : ObjectId("583f114e1cff44b7ab414dc1"),
"statusId" : ObjectId("583ee05a1d5161941632091a"),
"secondarySkills" : [],
"primarySkills" : [],
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("583ff108cfa71d942269b09b"),
"employeeId" : ObjectId("583f114e1cff44b7ab414dc4"),
"statusId" : ObjectId("583ee05a1d5161941632091a"),
"secondarySkills" : [],
"primarySkills" : [],
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("5848c40599fa37d40a7e7392"),
"employeeId" : ObjectId("583f114e1cff44b7ab414dc8"),
"secondarySkills" : [
ObjectId("5838373072d7bab017488ba2")
],
"primarySkills" : [
ObjectId("5848c3c299fa37d40a7e7390"),
ObjectId("5848c3d599fa37d40a7e7391")
],
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("5848c41699fa37d40a7e7393"),
"employeeId" : ObjectId("583f114e1cff44b7ab414dc6"),
"secondarySkills" : [],
"primarySkills" : [
ObjectId("5838373072d7bab017488ba2"),
ObjectId("5848c3c299fa37d40a7e7390"),
ObjectId("5848c3d599fa37d40a7e7391")
],
"__v" : 0
}
UseCase :
When i want to group EmployeeDetails collection based on Status ID, i used following aggregation in Mongoose :
EmployeeDetailsModel.aggregate([
{
$group: {_id: "$statusId", count: {$sum: 1}}
}
]).exec(...);
In similar way, i want to group based on primarySkills or secondarySkills where both of them are array of Skill ObjectID's.
I tried few approaches but no luck. Need some help.
So if you are trying to get a result that shows a list of employees who has a certain skill, $unwind might help.
db.emp.aggregate([{$unwind:"$primarySkills"},{$group:{"_id":"$primarySkills", "employees":{$push:"$employeeId"}}}])
And here's the result:
{ "_id" : ObjectId("5848c3d599fa37d40a7e7391"), "employees" : [ ObjectId("583f114e1cff44b7ab414dc6"), ObjectId("583f114e1cff44b7ab414dc8") ] }
{ "_id" : ObjectId("5848c3c299fa37d40a7e7390"), "employees" : [ ObjectId("583f114e1cff44b7ab414dc6"), ObjectId("583f114e1cff44b7ab414dc8") ] }
{ "_id" : ObjectId("5838373072d7bab017488ba2"), "employees" : [ ObjectId("583f114e1cff44b7ab414dc6") ] }
The $unwind doc.

Mongodb aggreate, cannot get sort to work on date field

Data:
{
"properties" : {
"user" : ObjectId("51d3053627f4169a52000005"),
"createdOn" : ISODate("2013-07-02T18:00:03.841Z")
},
"_id" : ObjectId("51d31a87716fb81a58000003"),
"geometry" : {
"type" : "Point",
"coordinates" : [ 10, 10 ]
}
},{
"properties" : {
"user" : ObjectId("51d3053627f4169a52000005"),
"createdOn" : ISODate("2013-07-02T18:23:03.841Z")
},
"_id" : ObjectId("51d31a87716fb81a58000003"),
"geometry" : {
"type" : "Point",
"coordinates" : [ 20, 20 ]
}
}
And I am trying the following query:
db.locations.aggregate(
{ $group: {
_id: "$properties.user",
locations: {
$push: {
location: {geometry: "$geometry", properties: "$properties"}
}
}
}},
{ $sort: { "properties.createdOn": 1 }}
);
No matter which direction I change the sort flag (1/-1) the order of my results does not change.
Any ideas?
After the $group, your pipeline docs only contain those fields defined in the $group, so properties.createdOn doesn't exist for the $sort operation.
Move your $sort before the $group in the pipeline instead:
db.locations.aggregate(
{ $sort: { "properties.createdOn": 1 }},
{ $group: {
_id: "$properties.user",
locations: {
$push: {
location: {geometry: "$geometry", properties: "$properties"}
}
}
}}
);

How to build query for relating parent document's field to embedded array document's field. Using MongoDB aggregation Operators

Consider the following document in the collection named 'CityAssociation'
{
"_id" : "MY_ID",
"ThisCityID" : "001",
"CityIDs" : [{
"CityID" : "001",
"CityName" : "Bangalore"
}, {
"CityID" : "002",
"CityName" : "Mysore"
}],
"CityUserDetails": {
"User" : "ABCD"
}
}
Now I have User value i.e. in above case I have value ABCD and want to find it with only city where the first level's field ThisCityID matches to the embedded array documnet's field CityID. Finally I need to project as follows (for the above case):
{
'UserName': 'ABCD',
'HomeTown':'Bangalore'
}
In Node.js + MongoDB native drive, I wrote a aggregation query as follows which is not working as expected.
collection.aggregate([
{ $match: { 'CityUserDetails.User': 'ABCD', 'CityIDs': { $elemMatch: { CityID: ThisCityID}}} },
{ $unwind: "$CityIDs" },
{ $group: {
_id: '$_id',
CityUserDetails: { $first: "$CityUserDetails" },
CityIDs: { $first: "$CityIDs" }
}
},
{ $project: {
_id: 0,
"UserName": "$CityUserDetails.User",
"HomeTown": "$CityIDs.CityName"
}
}
], function (err, doc) {
if (err) return console.error(err);
console.dir(doc);
}
);
Can anyone tell me how this can be done with query.
Note: On MongoDB schema we don't have control to change it.
You can use the $eq operator to check if the first level's field ThisCityID matches embedded array document's field CityID.
db.city.aggregate([
{ $match : { "CityUserDetails.User" : "ABCD" }},
{ $unwind : "$CityIDs" },
{ $project : {
matches : { $eq: ["$CityIDs.CityID","$ThisCityID"]},
UserName : "$CityUserDetails.User",
HomeTown : "$CityIDs.CityName"
}},
{ $match : { matches : true }},
{ $project : {
_id : 0,
UserName : 1,
HomeTown : 1
}},
])
And the result is:
{
"result" : [
{
"UserName" : "ABCD",
"HomeTown" : "Bangalore"
}
],
"ok" : 1
}

Resources