How to get top two salaried employees in mongodb - node.js

I want to get top two employees based on salary from a department,
Department Schema ::
const departmentSchema = mongoose.Schema({
name: {
type: String
},
employee_id: [{ type: mongoose.Schema.Types.ObjectId, ref: 'employee' }]
})
Employee Schema ::
const employeeSchema = mongoose.Schema({
name: {
type: String,
},
salary : {
type: Number
}
})
Let's say I have document in department schema as ::
{
"_id":ObjectId("615851c162a012f82cc5bdf6"),
"name":"abc",
"employee_id":[
ObjectId("615852d062a012f82cc5d54d"),
ObjectId("6158530462a012f82cc5d9c8"),
ObjectID("6158530462a012f82cc8e1b9")
]
}
And document in employee as ::
{
"_id":ObjectId("615852d062a012f82cc5d54d"),
"name":"john",
"salary":12345
}
{
"_id":ObjectId("6158530462a012f82cc5d9c8"),
"name":"smith",
"salary":999
}
{
"_id":ObjectId("6158530462a012f82cc8e1b9"),
"name":"Alex",
"salary":99999
}
Based on this I want to get details of top two employees based on their salary.
For this I tried as:
await department.aggregate([
{ $match : { name : 'abc' } },
]).populated('employee_id')
But it throws error populate is not a function. How can I work for the same as I am very much in MySQL but not familiar with mongodb? If anyone needs any further information please do let me know.
Expected response as ::
[
{
"_id":ObjectId("6158530462a012f82cc8e1b9"),
"name":"Alex",
"salary":99999,
department: abc
},
{
"_id":ObjectId("615852d062a012f82cc5d54d"),
"name":"john",
"salary":12345,
department: abc
}
]

You can do the followings in an aggregation pipeline:
$match to select the expected department document
$lookup from employee collection with a subpipeline
In the subpipeline of step 2, use $match to filter out expected employee records
In the same subpipeline , $sort by salary and $limit to get top 2 employee
use $addFields to add the department name to the employee object
$unwind the subpipeline result
$replaceRoot to get the employee documents
Here is the Mongo playground for your reference.

Related

How to get categories and sub-categories in single API response

I have two collections Categories and Subcategories inside a Categories collection. I have an array that is storing the ids of subcategories that are stored in Subcategories collection. Below is my document structure:
Categories collection
{
id:65,
title:"Automotive",
active:true,
subcategories:[35,28,30]
}
subcategories collection
{
id:35,
title:"Automotive technology",
category_id:65,
active:true
},
{
id:28,
title:"Automotive coatings",
category_id:65,
active:true
},
{
id:30,
title:"Machinery and equipments",
category_id:65,
active:true
}
As seen in above collection 3 documents from subcategories collection have been associated with the category document. I want to fetch data in below format on single API hit.
API response should be in below format:
{
data:{
category:{
id:65,
title:"Automotive",
subcategories:[{
id:35,
name:"Automotive technology",
},
{
id:28,
name:"Automotive coatings",
},
{
id:30,
name:"Machinery and equipments",
}]
},
category:{
id:66,
title:"Food",
subcategories:[{
id:23,
name:"Drinks",
},
{
id:2,
name:"Additives",
}]
},
},
messsage:"Success",
code:200
}
As of now I am able to get data in 2 api hits that is like first getting all the categories
const category = await db.categories.find({});
Then on click of some particular category fetching all the sub categories based on the category id.
const subCategories = await db.SubCategories.find({category_id:id});
How can I get the above data in desired format in single API hit?
You need something like this, also, if you use mongoose, you can use .populate()
To format data you can use $project stage in aggregation pipeline or projection in .find()
If you want to use Mongoose with populate:
CategorySchema:
const CategorySchema= new mongoose.Schema({
...
subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: 'SubCategory' }],
...
});
need _id column on reference table
ref content is must be equal to model name like
module.exports = mongoose.model('SubCategory', SubCategorySchema);
Controller:
const categories = await Category.find({})
.populate({
path: 'subCategories'
})
path content is must be equal to column name
If you want to write with Mongo query:
db.getCollection("categories").aggregate([
{
$lookup:
{
from: 'subCategories',
localField: 'subCategories',
foreignField: 'id',
as: 'subCategories'
}
}
])
You get empty object because you are not using "_id" field for join schemas. If you want to use "id" field u need to use virtuals on Mongoose:
https://mongoosejs.com/docs/tutorials/virtuals.html#populate

MongoDB - update data in array of objects within object

I have a document in mongoDB structured like that
_id: ObjectId("generatedByMongo"),
name: {
required: true,
type: String,
trim: true
},
last: {
required: true,
type: String,
trim: true
},
grades: [{
grade: {
_id: ObjectId(""),
grade: Number,
date: date
}
}]
And to server I send array of objects containing 3 fields
[
{studentId}, {gradeId}, {newGrade}
]
What I'm trying to accomplish is I want to find in within that user collection grade with given gradeId and update it's value to newGrade. As far as I tried to do that I have done this
router.patch('/students/updateGrade',async(req,res) => {
const studentId = req.body.updateGradeArray[0].studentId;
const gradeId = req.body.updateGradeArray[0].gradeId;
const newGrade = req.body.updateGradeArray[0].newGrade;
try {
const student = await Student.find({_id: studentId})
.select({'grades': {$elemMatch: {_id: gradeId}}});
} catch(e) {
console.log(e);
}
}
);
If you intend to update just grade.grade(the number value), try this:
Student.updateOne(
// Find a document with _id matching the studentId
{ "_id": studentId },
// Update the student grade
{ $set: { "grades.$[selectedGrade].grade": newGrade } },
{ arrayFilters: [{ "selectedGrade._id": gradeId }] },
)
Why this should work:
Since you are trying to update a student document, you should be using one of MongoDB update methods not find. In the query above, I'm using the updateOne method. Inside the updateOne, I am using a combination of $set and $[identifier] update operators to update the student grade.
I hope this helps✌🏾

$and operator returns incorrect result in mongodb

I am trying to find one product who's id matches given id and also want to know if it's quantity will still be greater or equal to the sold quantity (after adding the sold quantity which will be sold later in the process).
const product = await Product.findOne(
{
$expr: {
$and: [
{ _id: id },
{ $gte: ["$quantity", { $sum: ["$soldQuantity", quantity] }] }
]
}
}
);
(here quantity is a constant who's value is 2 and id is an ObjectId)
what I've achieved so far is if I turn $gte into $lte I get the right result but $gte returns me false result, which should return an empty object instead if conditions did not meet.
I'm using mongoDB version 4.2
Schema has:
Schema({
quantity: {
type: Number,
required: true
},
soldQuantity: {
type: Number,
default: 0
}
})

populate with condition in mongoose

I have a movie booking data like below
movie order schema
{
"movie_id": "5d64fb7975214a183bf10f5b",
"variant_id": "5d64fda8fc7f911a77afd55c",
}
and movie schema data like below
{
"_id":"5d64fb7975214a183bf10f5b",
"description":"Sahoo movie ",
"options":[
],
"variants":[
{
"enabled":true,
"_id":"5d64fda8fc7f911a77afd55c",
"variant_name":"",
"regular_price":345,
"sale_price":125,
"stock_quantity":45,
},
{
"enabled":true,
"_id":"5d661c8181a4572a27f048dd",
"variant_name":"",
"regular_price":120,
"sale_price":50,
"stock_quantity":10,
}
],
"on_sale":false,
"variable":true
}
now I'm trying to querying the movie order
let data = await MovieOrder.find().populate(movie_id)
but it is giving movie details with all the variant.but what I'm looking for here is
In the movie order what is the variant_id is present based on that I need to populate the movie with variant based on that variant id on the movie order
Note: the result should be, what are the variant_id in the movie order schema is equal to variant id in the movie schema
Hope you guys understand my problem, please give me the solution
With the way your schema is designed it is hard for populate to filter the movies variants array with the variant_id in the movies order
as this is not how populate works.
In order to use populate properly, you would have to change the movies schema making the variants array as ref to
Variants model. For example, your schema definitions would need to look like
Schema definitions
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const variantSchema = new Schema({
enabled: Boolean,
variant_name: String,
regular_price: Number,
sale_price: Number,
stock_quantity: Number
})
const Variant = mongoose.model('Variant', variantSchema)
const movieSchema = new Schema({
description: String,
options: [String],
variants: [{ type: 'ObjectId', ref: 'Variant' }],
on_sale: Boolean,
variable: Boolean
})
const Movie = mongoose.model('Movie', movieSchema)
await MovieOrder.find().populate('movie_id variant_id')
This way your query just returns the movie and the variant it needs.
However, if the current schema design remains as it is, you can use $lookup in an aggregate pipeline to do the populate and then filter the resulting array using $filter on the variant that matches the variant_id field in your MovieOrder model:
await MovieOrder.aggregate([
{ '$lookup': {
'from': 'movies',
'localField': "movie_id", // field in the movieorders collection
'foreignField': "_id", // field in the movies collection
'as': "movie"
} },
{ '$addFields': {
'movie': { '$arrayElemeAt': ['$movie', 0 ] }
} },
{ '$addFields': {
'movie.variants': {
'$filter': {
'input': '$movie.variants',
'cond': { '$eq': ['$variant_id', '$$this._id'] }
}
}
} },
]).exec()

Is it possible to have condition in mongoose populate

I have a sub document called services in a collection it contains serviceID and type. serviceID is referred to two collections internalservice and externalservice varies based on the type field.
internalServices:[{
serviceID:ObjectId('0000000000000'),
serviceDesc:'sweeping'
},
{
serviceID:ObjectId('0000000000000'),
serviceDesc:'floor cleaning'
}]
externalServices:[{
serviceID:ObjectId('0000000000000'),
serviceDesc:'painting'
},
{
serviceID:ObjectId('0000000000000'),
serviceDesc:'white washing'
}]
Above two are lookup collection. And here the company collection
_id:"xxxxxxxxxxxxxxxx",
name: 'xxxx',
Address:'xxx',
services:[{
serviceID:ObjectId('0000000000000'),
type: internalservice
},
{
serviceID:ObjectId('0000000000000'),
type: externalservice
}]
Here, i want to populate service description based on type.
Is it possible to have conditional path at populate?
Here's my code
Company.findOne({"_id": mongoose.Types.ObjectId(req.params.companyID)},
{})
.populate('services.serviceID','serviceDesc','InternalService')
.exec(function (err, companyInfo) {
console.log(companyInfo);
});
This populates internal service description but i need to populate external service description for type externalservice. When i have two populate the second populate replaces the result with null for unmatched documents.
You can include in populate where conditions like:
Story
.find(...)
.populate({
path: 'fans',
match: { age: { $gte: 21 }},
select: 'name -_id',
options: { limit: 5 }
})
.exec()
Here is the Mongoose Populate Documentation

Resources