Finding in Mongoose with specific parametres - node.js

I have a database in Mongodb filled with profiles with different skills.
Here's an example of one profile.
{_id: 570b8b5afdcaf27c24a0a837,
identifier: 'Mr X',
url: 'https://MRX.com',
email: 'Mrx#gmail.com'
skills:
[ { _id: 570b8b5afdcaf27c24a0a858, title: 'Java', number: '74' },
{ _id: 570b8b5afdcaf27c24a0a857, title: 'Linux', number: '48' },
{ _id: 570b8b5afdcaf27c24a0a856, title: 'C++', number: '43' },
{ _id: 570b8b5afdcaf27c24a0a855, title: 'SQL', number: '34' },
{ _id: 570b8b5afdcaf27c24a0a854, title: 'XML', number: '28' },
{ _id: 570b8b5afdcaf27c24a0a853, title: 'MySQL', number: '23' },
{ _id: 570b8b5afdcaf27c24a0a852, title: 'C', number: '22' },
{ _id: 570b8b5afdcaf27c24a0a851,
title: 'Java Enterprise Edition',
number: '18' }]
}
My question: is there a query in mongoose where I can find a profile who has linux in his skills but also the number of that linux skill is greater than 40?
I tried with something like this in the finding option :
var x = {
'skills.title': 'Linux',
'skills.number': {
$gt: 40
},
}
but it doesn't work,the program finds the Linux skill but number 40 is not associated with Linux.
So is there a query to solve my problem?

Use $elemMatch:
db.yourCollection.find(
{ skills: { $elemMatch: { title: "Linux", number: { $gte: 40 } } } }
)
Taken from the docs:
The $elemMatch operator matches documents that contain an array field
with at least one element that matches all the specified query
criteria.

In mongoose you should do something like this:
var myCol = require('.myModel');
myCol.aggregate(
[
{$match:
{
title: "Linux",
number: { $gte: 40 }
}
}
], function (err, result) {
if (err) {
console.log(err);
} else {
console.log(result);
}
});

Related

Mongoose get documents where id does NOT exist inside nested object

I am trying to query a list of documents where a userid DOES NOT exist inside an array of objects.
The database (documents) looks like this:
[
{
title: 'object 1',
description: 'description 1',
members: [
{ profile: { id: '123', ...}, data: {} },
{ profile: { id: 'abc', ...}, data: {} },
{ profile: { id: 'def', ...}, data: {} },
]
},
{
title: 'object 2',
description: 'description 3',
members: [
{ profile: { id: 'aaa', ...}, data: {} },
{ profile: { id: 'bbb', ...}, data: {} },
{ profile: { id: 'ccc', ...}, data: {} },
]
},
]
Given that my userid is 'aaa' I am trying to query all documents where I am NOT a member.
I can successfully query all documents where my userid exists using this code:
await this._repository.findManyByQuery(
{
members: {
$elemMatch: {
"profile.id": "aaa",
},
},
},
)
However I am looking to query all objects where my ID DOES NOT exist. I have tried using $ne however it still returns the documents where the user id exists
members: {
$elemMatch: {
"profile.id": { $ne: "aaa" },
},
},
I guess I am looking for the opposite of $elemMatch but for querying inside an arry
You can use $not to negate the $elemMatch like this:
await this._repository.findManyByQuery({
members: {
"$not": {
$elemMatch: {
"profile.id": "aaa"
}
}
}
})
Example here

How to query two Collections and return one response?

I am trying to get the result of movies + the average rating for each movie.
The information lays in two different collections. The rating collection holds the movie ID.
Movies - the main collection
[
{
name: "The Matrix"
genres: [
"5e80bc463c410a7291dee8c1",
"5e80bc463c410a7291dee8c2"
],
},
{
name: "The Matrix 2"
genres: [
"5e80bc463c410a7291dee8c1",
"5e80bc463c410a7291dee8c2"
],
}
]
Ratings
[
{
movie: "5e3355c206c7cd030e48b8bc",
user: "5e3355c206c7cd030e48b8b4",
rating: 10
},
{
movie: "5e3355c206c7cd030e48b34c",
user: "5e3355c206c7cd030e48b544",
rating: 7
},
{
movie: "5e3355c206c7cd030e4823bc",
user: "5e3355c206c7cd03344823bc",
rating: 5
}
}
I want the following result:
{
"total": 1,
"page": 0,
"perPage": 25,
"pageSize": 1,
"movies": [
{
_id: "5e3b0e57870c15002e29a238",
name: 'The Matrix',
genres: ['Action', 'Sci-Fi'],
rating: 10
},
{
_id: "5e3b0e57870c15002e29a239",
name: 'The Matrix 2',
genres: ['Action', 'Sci-Fi'],
rating: 10
},
]
}
I don't understand how to add the average rating for each movie into the query result.
My current query looks like this:
Movie.find(demand)
.select("name genres")
.skip(page * limit)
.limit(limit)
.populate('genres', ['name'])
.lean()
.exec((err, movies) => {
if (err) {
return res.json(err);
}
Movie.countDocuments(demand).exec((count_error, count) => {
if (err) {
return res.json(count_error);
}
return res.status(200).json({
total: count,
page: page,
perPage: limit,
pageSize: movies.length,
movies: movies
});
});
});
In mongoose terms, you are looking for "reverse populate", which (as far as I know) does not come with mongoose out of the box. (See e.g. Reverse populate in mongoose).
Instead what you can do is make an aggregate mongo query, like so:
Movie.aggregate([{
$match: demand
}, {
$skip: page * limit
}, {
$limit: limit
}, {
$lookup: {
from: "ratings",
localField: "_id",
foreignField: "movie",
as: "ratings"
}
}, {
$project: {
name: 1,
genres: 1,
ratting: {$avg: "$ratings.rating"}
}
}]).exec((err, movies) => {
if (err) {
return res.json(err);
}
Genre.populate(movies, {path: 'genres', select: 'name'}, (err, movies) => {
if (err) {
return res.json(err);
}
// rest of your code to count etc.
});
});

How to find Mongoose data recursively?

I am newbie in MEANJS and i have a problem i.e, there are collection called employee and have multiple documents with their boss field. Now i want get all employees with their lower level.
For example:-
1) {_id:ObjectId('587dcd3edca5f235f862fdfd'), name:'John'} //he doesn't have boss
2) {_id:ObjectId('587dcd3edca5f235f86dddew'), name: 'Jimmy', 'boss': ObjectId('587dcd3edca5f235f862fdfd')} //john is boss
3) {_id:ObjectId('587dcd3edca5f235f863ew'), name: 'David', 'boss': ObjectId('587dcd3edca5f235f86dddew')} //john,Jimmy are bosses
4) {_id:ObjectId('587dcd3edca5f235f86qwa'), name: 'Dyan', 'boss': ObjectId('587dcd3edca5f235f86dddew')} //john,Jimmy,David are bosses
5) {_id:ObjectId('587dcd3edca5f235f8ew32'), name:'Jack', 'boss': ObjectId('587dcd3edca5f235f862fdfd')} //john is boss
6) {_id:ObjectId('587dcd3edca5f2wsw23rlot'), name: 'Loren', 'boss':ObjectId('587dcd3edca5f235f8ew32')} //john,Jack is boss
If we take
Jonh then output will ['Jimmy','Jack','David','Dyan','Loren']
Jack then output will ['Loren']
Here is my try code:-
getBosses(user._id)
function getBosses(id){
User.find({boss:id})
.exec(function(err,users){
if(err)
return console.log(err);
//How handle here 'users' array
//for something getBosses call recursively
})
}
As far as I understood you need to find all subordinates of that people. I think the best way to do it is using $graphLookup.
db.bosses.insertMany([
{ _id: "587dcd3edca5f235f862fdfd", name: "John" },
{
_id: "587dcd3edca5f235f86dddew",
name: "Jimmy",
boss: "587dcd3edca5f235f862fdfd",
},
{
_id: "587dcd3edca5f235f863ew",
name: "David",
boss: "587dcd3edca5f235f86dddew",
},
{
_id: "587dcd3edca5f235f86qwa",
name: "Dyan",
boss: "587dcd3edca5f235f86dddew",
},
{
_id: "587dcd3edca5f235f8ew32",
name: "Jack",
boss: "587dcd3edca5f235f862fdfd",
},
{
_id: "587dcd3edca5f2wsw23rlot",
name: "Loren",
boss: "587dcd3edca5f235f8ew32",
},
]);
db.bosses.aggregate([
{
$graphLookup: {
from: "bosses",
startWith: "$_id",
connectFromField: "_id",
connectToField: "boss",
as: "subordinates",
},
},
{
$project: {
_id: false,
name: true,
subordinates: {
$reduce: {
input: "$subordinates",
initialValue: "",
in: { $concat: ["$$value", ", ", "$$this.name"] },
},
},
},
},
{
$project: {
name: true,
subordinates: { $substrBytes: ["$subordinates", 2, -1] },
},
},
]);
The result of the last one is:
[
{ name: 'John', subordinates: 'David, Dyan, Loren, Jack, Jimmy' },
{ name: 'Jimmy', subordinates: 'David, Dyan' },
{ name: 'David', subordinates: '' },
{ name: 'Dyan', subordinates: '' },
{ name: 'Jack', subordinates: 'Loren' },
{ name: 'Loren', subordinates: '' }
]
The most important thing is $graphLookup stage of the aggregate pipeline. Last two $project stages is just response formatting - return only name and subordinates as string field with comma separated names.
To get data for a specific person you can use $match stage before $graphLookup like that:
db.bosses.aggregate([
{ $match: { name: "John" } },
{
$graphLookup: ...

Find object id in object ids array returns empty array using Mongoose

I have two Mongoose schemas:
var EmployeeSchema = new Schema({
name: String,
servicesProvided: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Service'
}]
});
var ServiceSchema = new Schema({
name: String
});
I'm trying to find employees who provide a specified service with the service ID I send into the http request. This is my code:
Employee
.find({
servicesProvided: req.params.service_id
})
.exec(function(err, employees) {
if (err) {
console.log(err);
res.send(err);
} else {
res.json(employees);
}
});
The problem is that this code returns an empty array and I don't know why. I've tried a lot of things like casting the service id to mongoose.Schema.Types.ObjectId but it doesn't work.
Any idea? I'm using Mongoose 3.8.39. Thanks!
In your EmployeeSchema, servicesProvided is an array, to filter employees by that field you should use $in operator:
var services = [req.params.service_id];
Employee.find({
servicesProvided: {
$in: services
}
}, ...
I think you need $elemMatch! From docs:
{ _id: 1, results: [ { product: "abc", score: 10 }, { product: "xyz", score: 5 } ] },
{ _id: 2, results: [ { product: "abc", score: 8 }, { product: "xyz", score: 7 } ] },
{ _id: 3, results: [ { product: "abc", score: 7 }, { product: "xyz", score: 8 } ] }
Search like:
db.survey.find({ results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } })
Results in:
{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "xyz", "score" : 8 } ] }
But since you're doing a single query condition (look at the docs again) you can replace
db.survey.find(
{ results: { $elemMatch: { product: "xyz" } } }
)
with
db.survey.find(
{ "results.product": "xyz" }
)
So in your case it should be something like:
find({
'servicesProvided': ObjectId(req.params.service_id)
})

mongodb aggregation: average and sorting

I am quite a newbie and have a problem for mongodb aggregation. I used mongoose.
var SubjectScore = new Schema({
name: {type:String, required:true}, //math, science, history, ...
score: {type:Number, required:true } // 95, 85, 77,....
});
var Subject = new Schema({
year: Number, //2012, 2013, 2014
subjectScore : [SubjectScore]
});
var StudentSchema = new Schema({
name: String,
subject: [Subject], //array length varies for each student
profile: String,
});
So, Input data is like this.
{ _id: 54c921aaa7918d4e4a8c7e51,
name: John,
profile: "He is nice",
subject: [{ year: 2010,
subjectScore: [{ name:"history" score:66},
{ name:"math", score:65},
{ name:"science", score:87}] }]
},{ year: 2011,
subjectScore: [{ name:"history" score:75},
{ name:"math", score:61},
{ name:"science", score:92}] }]
},{ year: 2012,
subjectScore: [{ name:"history" score:83},
{ name:"math", score:82},
{ name:"science", score:86}] }]
},{ year: 2013,
subjectScore: [{ name:"history" score:77},
{ name:"math", score:99},
{ name:"science", score:71}] }]
}]
}
The final result I want to get is like follows.
[
{ _id: "54c921aaa7918d4e4a8c7e51",
name: "John"
profile: "He is nice",
avgScore: [
{name: "math", score: 77},
{name:"history", score:78},
{name:"science", score:86} ]
totalAvg: 82
},
{ _id: "54c921aaa7918d4e4a8c7e5b",
name: "Mary"
profile: "She is kind",
avgScore: [
{name: "math", score: 67},
{name:"history", score:99},
{name:"science", score:96} ]
totalAvg: 82
},
{ _id: "54c921aaa7918d4e4a8c7e56",
name: "Jane"
profile: "She is smart",
avgScore: [
{name: "math", score: 99},
{name:"history", score:99},
{name:"science", score:99} ],
totalAvg: 99
}
..... // 7 more student for first page result
]
I tried following but I couldn't get the field of name, profile. So I needed additional query for getting name&profile fields and sorting again.
{$project:{subject:1}},
{$unwind:"$subject"},
{$unwind:"$subject.subjectScore"},
{$group:{_id:{studentId:"$_id", subjectName:"$subject.subjectScore.name"},
avgScore:{$avg: "$subject.subjectScore.score"}}},
{$group:{_id:"$_id.studentId",
avgScore:{$push: {name:"$_id.subjectName", score:"$avgScore"}},
totalAvg:{$avg:"$avgScore"}}},
{$sort:{totalAvg:-1}},
{$limit:10} // for first page (students per page : 10)
I want know how to keep the fields which is not necessary for aggregation but need to show as result. If it's not possible, do I need additional queries for result I want?
Are there another way to get this result considering performance?
I've already put a few days for this job and googling, but didn't get an answer. Help me please.
Thank you.
You are losing the information in your first operation because the $project is only sending along the value of the subject key.
The operation to keep the value of fields unaffected by aggregation calculations (name and profile) is with $first. It will just grab the first value of that field which should be the same as the rest of them.
{ $unwind: "$subject" },
{ $unwind: "$subject.subjectScore" },
{ $group: { _id: { sId: "$_id", subjectName: "$subject.subjectScore.name" },
avgScore: { $avg: "$subject.subjectScore.score" },
name: { $first: "$name" },
profile: { $first: "$profile" } } },
{ $group: { _id: "$_id.sId",
name: { $first: "$name" },
profile: { $first: "$profile" },
avgScore: { $push: { name: "$_id.subjectName", score: "$avgScore" } },
totalAvg: { $avg: "$avgScore" } } }
{ $sort: { totalAvg: -1 } }, // this is sorting desc, but sample is asc?
{ $limit: 10 }

Resources