Mongoose sort and search document by populated field - node.js

I have collections user, news, and user-news."user-news" populated to "user" and "news".
Can I sort and search document of "user-news" by "user.name" or "news.title" ?
const usersSchema = new Schema({ name: String })
const newsSchema = new Schema({ title: String }),
UsersNewsModel.find(queryObj)
.populate('user', 'name')
.populate('news', 'title')
.sort({ 'user.name': -1 })
.exec((findError, records) => {...}

Mongoose populate doesn't support sorting from populated field. But you can do something like this with aggregate.
UsersNewsModel.aggregate([
{
$lookup: {
from : 'user',
localField : 'user',
foreignField: '_id',
as : 'user'
}
},
{
$unwind: {
path: $user
}
},
{
$sort: {
'user.name': -1
}
}
])

Related

Aggregate Function Mongoose - Node

I have a schema
const membershipsSchema = new Schema({
spaceId: {
type: Schema.Types.ObjectId,
ref: 'Space',
},
member: {
type: Schema.Types.ObjectId,
ref: 'User',
},
....
);
mongoose.model('Membership', membershipsSchema);
I want to use join statement like
Select * from membershipPlans as plans join User as users on plans.member=users._id
where plans.spaceId=id and users.status <> 'archived'; // id is coming from function arguments
I tried the aggregate pipeline like
const memberships = await Memberships.aggregate([
{
$match: {
spaceId: id
}
},
{
$lookup: {
from: 'User',
localField: 'member',
foreignField: '_id',
as: 'users',
},
},
{
$match: {
'users.status': {$ne: 'archived'}
}
},
]);
But on console.log(memberships); I am getting an empty array. If I try return Memberships.find({ spaceId: id }) it returns populated memberships of that space. But when I try
const memberships = await Memberships.aggregate([
{
$match: {
spaceId: id
}
},
]}
It still returns an empty array. Not sure if I know how to use an aggregate pipeline.
There are two things that you need to do:
Cast id to ObjectId.
Instead of using $match, just filter the contents of the users array using $filter.
Try this:
const memberships = await Memberships.aggregate([
{
$match: {
spaceId: new mongoose.Types.ObjectId(id)
}
},
{
$lookup: {
from: 'User',
localField: 'member',
foreignField: '_id',
as: 'users',
},
},
{
$project: {
users: {$filter: {
input: "$users",
as: "user",
cond: {
$ne: ["$$user.status", "archived"]
}
}}
}
},
]);

Get Data from the different collection without any relation in mongoose

Get Data from the different collection without any relation in mongoose
Collection Schema
mongoose.Schema({
skillid:{type:Number},
name:{type:String},
});
Skill Collection
mongoose.Schema({
userid: {type: mongoose.Types.ObjectId},
OverView:{type:String},
location:{type:String},
skills:[{Type:Number}]
});
{
_id: 5f48d5d1b98bffee67b49917
skillid: 1
name: 'HTML'
}
{
_id: 5f48d612b98bffee67b49919
skillid: 2
name: 'PHP'
}
User Collection
{
_id: 5f425bdb311b791670d60de6,
userid: 5f41115fbd904134883ae2d8,
OverView: 'sdsdssdsd',
skills: [1,2], // skill id
Education: [ 5f453e7f53895727f0e39d82, 5f453fb963d4ab181c115982 ],
location: 'India',
}
How can u get skill name from Skill Collection - mongoose
i want result like this
{
_id: 5f425bdb311b791670d60de6,
userid: 5f41115fbd904134883ae2d8,
OverView: 'sdsdssdsd',
skills: ['HTML','PHP'], // skill id
Education: [ 5f453e7f53895727f0e39d82, 5f453fb963d4ab181c115982 ],
location: 'India'
} ```
You can use aggregate(),
$lookup join skill collection, match skills in skill collection
$addFields to add skill name in array format using $reduce
db.user.aggregate([
{
$lookup: {
from: "skill",
localField: "skills",
foreignField: "skillid",
as: "skills"
}
},
{
$addFields: {
skills: {
$reduce: {
input: "$skills",
initialValue: [],
in: {
$concatArrays: [["$$this.name"], "$$value"]
}
}
}
}
}
])
Playground
Another option you can use mongoose Virtual and also look at this
// SKILL SCHEMA
const SkillSchema = mongoose.Schema({
skillid:{type:Number},
name:{type:String},
});
// USER SCHEMA
const UserSchema = mongoose.Schema({
userid: {type: mongoose.Types.ObjectId},
OverView:{type:String},
location:{type:String},
skills:[{Type:Number}]
});
// CREATES VIRTUAL CONNECTION WITH SKILL COLLECTION
UserSchema.virtual('skills', {
ref: 'Skill', // The model to use
localField: 'skills', // Find people where `localField`
foreignField: 'skillid', // is equal to `foreignField`
// If `justOne` is true, 'members' will be a single doc as opposed to
// an array. `justOne` is false by default.
justOne: false,
// you can use other options
// options: { sort: { name: -1 }, limit: 5 }
});
// CREATE MODELS
const User = mongoose.model('User', UserSchema);
const Skill = mongoose.model('Skill', SkillSchema);
// QUERY TO FIND USERS AND POPULATE SKILLS
User.find({}).populate('skills').exec(function(error, user) {
console.log(user);
});
Note: please refer documentation, if you are getting any problem, This is not tested code!

Mongoose: Aggregation return empty value

I'm using aggregation to join 3 collections. But the result of the join is an empty array[].
This is my model for all 3 collections along with queries for aggregate.
Result of console.log(timetable) return []. I;m using references to refer a field in Timetable with corresponding collection.
Timetable Models
var TimetableSchema = new mongoose.Schema ({
timeslot: {
required: true,
'type': String,
},
classroom :{
type: mongoose.Schema.Types.ObjectId,
ref: 'Classroom'
},
subject :{
type: mongoose.Schema.Types.ObjectId,
ref: 'Subject'
},
teacher :{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
day :{
type:String,
required: true,
},
year :{
type:String,
required: true,
},
session :{
type:String,
required:true,
}
})
Classroom Models
var ClassroomSchema = new mongoose.Schema ({
classroom_name: {
type:String,
required:true,
unique: true,
},
classroom_blok:{
type:String,
required:true,
},
classroom_floor: {
type:String,
required:true,
},
});
Subject
var SubjectSchema = new mongoose.Schema ({
subject_id: {
required: true,
'type': String,
'default': shortid.generate
},
subject_name: {
type:String,
required:true,
},
subject_darjah:{
type:String,
required:true,
}
});
Agregate query
router.get('/today_absentee/view/:id',function(req,res){
Teacher.findById(req.session.userId).exec(function (error, user){
if (error){
return next(error);
}else
{
Teacher.find({_id:req.params.id }).exec(function(err, teacher){
if(err)
{
return next(err);
}else
{
Timetable.aggregate([
{
// This is doing the same thing as the previous .find()
$match: { teacher:req.params.id}
},
{
$lookup:{
from: "Classroom", // other table name
localField: "classroom", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "classroom" // alias for userinfo table
}
},
{ $unwind:"$classroom" }, // $unwind used for getting data in object or for one record only
{
$lookup:{
from: "Subject", // other table name
localField: "subject", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "subject" // alias for userinfo table
}
},
{ $unwind:"$subject" }, // $unwind used for getting data in object or for one record only
// define some conditions here
{
$match:{
$and:[{teacher:req.params.id}]
}
},
// define which fields are you want to fetch
{
$project:{
subject_name : "$subject.subject_name",
classroom : "$classroom.classroom_name",
}
}
]).exec(function(err, timetable)
{
// The query output is such that `classroom.classroom_name`
// value is unique for each document
if (err) throw err;
console.log(currentYear);
console.log(timetable);
res.render('creator_content/today_absentee_id',{timetable:timetable, user:user, teacher:teacher});
});
}
});
}
});
});
Models for all 3 collection.
Import mongoose at the top of your file and then use mongoose.Types.ObjectId() when matching against any object id fields in an aggregate query.
I have also used the $addFields aggregation pipeline to add the 2 fields (subject_name, classroom), and then output them in the next stage using $project.
const mongoose = require("mongoose");
router.get('/today_absentee/view/:id', function(req, res) {
Teacher.findById(req.session.userId).exec(function(error, user) {
if (error) {
return next(error);
} else {
Teacher.find({
_id: req.params.id
}).exec(function(err, teacher) {
if (err) {
return next(err);
} else {
Timetable.aggregate([
{
// This is doing the same thing as the previous .find()
$match: {
teacher: mongoose.Types.ObjectId(req.params.id)
}
},
{
$lookup: {
from: "Classroom", // other table name
localField: "classroom", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "classroom" // alias for userinfo table
}
},
{
$unwind: "$classroom"
}, // $unwind used for getting data in object or for one record only
{
$lookup: {
from: "Subject", // other table name
localField: "subject", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "subject" // alias for userinfo table
}
},
{
$unwind: "$subject"
}, // $unwind used for getting data in object or for one record only
// define some conditions here
{
$match: {
$and: [{
teacher: mongoose.Types.ObjectId(req.params.id)
}]
}
},
// Add new field names
{
$addFields: {
subject_name: "$subject.subject_name",
classroom: "$classroom.classroom_name",
}
},
// define which fields are you want to fetch
{
$project: {
subject_name: 1,
classroom: 1,
}
}
]).exec(function(err, timetable) {
// The query output is such that `classroom.classroom_name`
// value is unique for each document
if (err) throw err;
console.log(currentYear);
console.log(timetable);
res.render('creator_content/today_absentee_id', {
timetable: timetable,
user: user,
teacher: teacher
});
});
}
});
}
});
});

Mongoose populate in array

I try to play with populate but without success ...
It's possible to do this?
I have 2 shema :
- User
import mongoose, { Schema } from 'mongoose'
const userSchema = new Schema({
email: { type: String, unique: true },
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
products: [{
productId: { type: Schema.Types.ObjectId, ref: 'Product' },
dateAdd: { type: Date, default: Date.now }
}]
}, { timestamps: true })
const User = mongoose.model('User', userSchema)
export default User
And Product :
import mongoose, { Schema } from 'mongoose'
const productSchema = new Schema({
domain: String,
originUrl: { type: String },
price: Number,
img: String,
userFollow: [{ type: Schema.Types.ObjectId, ref: 'User' }]
})
const Product = mongoose.model('Product', productSchema)
export default Product
So I want to retrieve all the info for each of my prodcutId
I try this way (and many others without success):
User.findOne({ _id: userId }).populate({
path: 'products.productId',
populate: { path: 'products.productId', model: 'Products' }
}).exec(function (err, products) {
if (err) {
console.log('errors :' + err)
}
console.log('Product => ' + util.inspect(products))
})
Populate has no effect, same result with just the findOne()
I think User.findOne({ _id: userId }).populate('products.productId') should work.
Try using aggregate function of MongoDB and $lookup.
Users.aggregate([
{
"$match":
{
_id: user.id
}
},
{
"$lookup":
{
from: "Product",
localField: "products",
foreignField: "_id",
as: "products"
}
}
])
.exec()
.then((result) => {
//your result
})
.catch((err) => {
// if any error
});

Many to many relationships MongoDB mongoose

I have a user schema and users can be friends.
I have a friendship schema:
var friendshipSchema = mongoose.Schema({
from : { type: Schema.Types.ObjectId, ref: 'User' },
to : { type: Schema.Types.ObjectId, ref: 'User' }
});
Now, I want to get all friends from an user foo:
Friendship
.find({
$or : [
{ from : foo._id },
{ to : foo._id }
]
})
The problem is: I want to populate all friends from foo, but not foo himself (since for X friends I would have X foos populated).
How could I achieve that?
Thanks!
This is what I did:
Friendship
.find({
$or : [
{ from : user._id },
{ to : user._id }
]
})
.populate({
path: 'from',
match: { _id: { $ne: user._id }},
select: '-token'
},{
path: 'to',
match: { _id: { $ne: user._id }},
select: '-token'
})
.lean()
.exec(function(err, retData)
{
// Do something here...
});

Resources