How to group over Array elements using Mongoose Aggregation FrameWork - node.js

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.

Related

How to find data with condition on a second collection with Mongoose

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

Remove object from nested array of objects

I got this user schema
const UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true,
},
groups: [
{
groupName: {
type: String,
required: true,
},
groupMembers: [{ type: Schema.Types.ObjectId, ref: "GroupMember" }],
},
],
});
And I want to delete a group from the ‘groups’ array based on given ‘userId’ and ‘groupId’, my attempt (with express and mongoose):
router.delete(
"/:userId/:groupId",
catchAsync(async (req, res) => {
const { userId, groupId } = req.params;
const updatedUser = await User.findByIdAndUpdate(
userId,
{ $pull: { "groups.$._id": groupId } },
{ new: true }
);
res.send({ updatedUser });
})
);
The response of the request: I get an error: “The positional operator did not find the match needed from the query.”
Edit:
After I delete a group I need to remove all the group members in the groupMembers array.
User collection structure example:
{
"_id" : "111",
"email" : "michael#gmail.com",
"username" : "michael098",
"groups" : [
{
"_id" : "222"
"groupName" : "family",
"groupMembers" : [
{
"_id" : "333"
"name" : "Liam"
},
{
"_id" : "444"
"name" : "Noah"
}
]
},
{
"_id" : "555"
"groupName" : "friends",
"groupMembers" : [
{
"_id" : "666"
"name" : "Oliver"
}
]
}
]
}
Inside every group there is group members and I have a collection for the group members that I ref in the UserSchema : groupMembers: [{ type: Schema.Types.ObjectId, ref: "GroupMember" }]
GroupMember collection structure example:
{
{
"_id" : "333"
"name" : "Liam"
},
{
"_id" : "444"
"name" : "Noah"
},
{
"_id" : "666"
"name" : "Oliver"
}
}
For example when I get the params of userId="111" and groupId="222" I will delete the whole 'family' group and the whole group members in the groupMembers array (Liam and Noah) from the GroupMember collection.
GroupMember collection after deleting the group with _id="222":
{
{
"_id" : "666"
"name" : "Oliver"
}
}
Assuming an actual doc might look like this (using strings instead of ObjectId to keep the example tighter):
{_id:1, groups: [ { type: "ID1", ref: "XX" }, { type: "ID2", ref: "G1" }, { type: \
"ID3", ref: "G2" } ] }
then this update will remove the subdoc in the groups array where _id = 1 and type = ID2:
db.foo.update({_id:1},
{ $pull: { groups: { type: "ID2" } }}
);

Inserting a document in mongoose with empty sub document(Array), creates empty document?

const mongoose = require('mongoose');
const orgSchema = new mongoose.Schema({
org_name : {
type : String,
required : true
},
admin : {
type : String,
required : true
},
branches : [{
branch_name : {
type : String,
required :true
},
branch_admin : {
type : String,
required : true
},
branch_locations : [{
_id : false,
sub_branch_name : {
type : String
},
sub_branch_admin : {
type : String
}
}]
}],
plan : {
type : mongoose.Schema.Types.ObjectId,
ref : 'plan',
required : true
},
status : {
type : String,
enum : ['ACTIVE', 'EXPIRED']
}
});
orgSchema.createIndex = ({
org_name : 1
},{
unique : true
});
module.exports = mongoose.model('organizations', orgSchema);
//Above is schema
{
"_id" : ObjectId("5bf91a1edc7ed22ca90d4624"),
"org_name" : "xxxxxxxxxxxxxxx",
"admin" : "sssssss",
"branches" : [
{
"_id" : ObjectId("5bf91a1edc7ed22ca90d4625"),
"branch_name" : "ssssss",
"branch_admin" : "ddd",
"branch_locations" : [
{}//automatically creates empty doc when no fields
]
}
],
"plan" : ObjectId("5bf91a1edc7ed22ca90d4623"),
"status" : "ACTIVE",
"__v" : 0
}
when i try to insert a document with empty fields in the sub document array, it returns with {} in array of sub document.
How to get rid from the new empty document in sub document. I should get an empty array when there is no field in the sub document to save...

MongoDB Query SubDocuments by field valeu

So I have this schema for a Supplier:
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Address = require('./Address.js'),
AddressSchema = mongoose.model('Address').schema,
Product = require('./Product.js'),
ProductSchema = mongoose.model('Product').schema;
// Create a new schema for the reviews collection with all the relevant information for a review
var Schema = mongoose.Schema;
var Supplier = new Schema(
{
name: String,
address: AddressSchema,
location: {
type: {type:String, default: 'Point'},
coordinates: [Number] // [<longitude>, <latitude>]
},
products: [ProductSchema]
}
);
Supplier.index({location: '2dsphere'});
var SupplierModel = mongoose.model('Supplier', Supplier );
// export the review model
module.exports = SupplierModel;
Products in my system have a "verified" field which is a boolean. In one of my routes I would like to query the DB to find all the suppliers which have products which aren't verified such that I can then render those products in the page.
I tried this, but unofrtunatelly it returns all the subdocuments no matter if "verified" is true or false:
exports.admin_accept_product_get = function (req, res) {
Supplier.find({'products.verified' : false}, function(err, docs) {
res.render('admin_accept_product', { user : req.user, suppliers: docs });
});
};
Any help is appreciated
Edit:
The previous query would return the following data:
{
"_id" : ObjectId("5b2b839a2cf8820e304d7413"),
"location" : {
"type" : "Point",
"coordinates" : [
-16.5122377,
28.4028329
]
},
"name" : "Tienda1",
"products" : [
{
"verified" : true,
"_id" : ObjectId("5b2b83d32cf8820e304d7420"),
"title" : "Vodka",
"inStock" : 15,
"typeOfItem" : "alcohol",
"sellingPrice" : 15,
"image" : "public/upload/15295784515201529168557789bottle.png",
"typeOfAlcohol" : "vodka"
},
{
"verified" : false,
"_id" : ObjectId("5b2b848f8c59960c44df09cd"),
"title" : "Whisky",
"inStock" : 40,
"typeOfItem" : "alcohol",
"sellingPrice" : 15,
"image" : "public/upload/15295786395491529323314298whisky.png",
"typeOfAlcohol" : "whisky"
}
],
"__v" : 2
}
I would like my query to not return the firt product because "verified == true"
You need to use $elemMatch to find the document and $elemMatch for projection of the data
db.collection.find({
products: {
$elemMatch: {
verified: false
}
}
},
{
products: {
$elemMatch: {
verified: false
}
},
location: 1
})
Output
[
{
"_id": ObjectId("5b2b839a2cf8820e304d7413"),
"products": [
{
"_id": ObjectId("5b2b848f8c59960c44df09cd"),
"image": "public/upload/15295786395491529323314298whisky.png",
"inStock": 40,
"sellingPrice": 15,
"title": "Whisky",
"typeOfAlcohol": "whisky",
"typeOfItem": "alcohol",
"verified": false
}
]
}
]
Check it here

How to update a multiple records in an array with sub dictionary in MongoDB

I am trying to update multiple records in an array with in sub dictionary. If where ever that record is found in a collection that record must be update.
My mongoose schema like this:
comments: [{
text: {
type: String,
},
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
name: {
type:String
},
photo:{
type:String
}
}
}]
**My data like this:**
comments : [
{
"text" : "Good one",
"_id" : ObjectId("5722101611a53cca08544d25"),
"author" : {
"id" : "123",
"name" : null,
"photo" : null
}
},
{
"text" : "nice one",
"_id" : ObjectId("5722103511a53cca08544d24"),
"created" : ISODate("2016-04-28T13:29:25.444Z"),
"author" : {
"id" : "123",
"name" :null,
"photo":null
}
},
{
"text" : "Good one",
"_id" : ObjectId("5718768d98eb065c035b645"),
"author" : {
"id" : "456"
}
}]
I want to update name and photo in the the comments array based on the author.id. In the above data to update author.id=123
My code is:
Event.find({'comments.author.id':_id},(err,event)=>{
_.each(event,function(eventData){
_.each(eventData.comments,function(commentData){
if(commentData.author.id ==_id){
console.log("event:"+eventData._id)
Event.update({_id:eventData._id},{$set:{'comments':[{'author.name':username,'author.photo':photoUrl}]}},{multi:true},(err,updateComment)=>{
console.log("commentupdate:"+JSON.stringify(updateComment))
});
}
})
})
})
I am stuck here from last two days.Please give me any solution.Don't make it as duplicate, If it is duplicate please provide a valid answer. Thanks

Resources