Get Data from the different collection without any relation in mongoose - node.js

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!

Related

How to perform FIND, then perform AND, then perform OR with POPULATE in Mongoose

I have a query that I wish to perform something similar as:
Archive does not exists AND
Owner Email in schema is in Query OR
Owner Email in Populated's schema is in Query
Below is what I have tried as of my understanding
let docs = await Document.find({ archive: { $exists: false }})
.and([{ owner_email: { $regex: localQuery } }])
.or()
.populate('owner_id', null, {
email: { $regex: localQuery },
});
So what I wish to do is, I have two schema, the user and the documents, User sometimes is shared along [as a librarian], then I wish to return, both, which matches the populated email or the actual owner's email.
As mongoose's populate() method does not really "join" collections and rather makes another query to the database to populate after the find() operation, you can switch to an aggregation pipeline and use $lookup in order to match the email in the referenced field. So assuming your models look like:
const Document = mongoose.model('Document', {
name: String,
archive: String,
owner_email: String,
owner: {type: Schema.Types.ObjectId, ref: 'Person'}
});
const Person = mongoose.model('Person', {
firstName: String,
lastName: String,
email: String
});
Then, you can do:
const result = await Document.aggregate([
{
$lookup: {
from: Person.collection.name,
localField: "owner",
foreignField: "_id",
as: "referencedOwner"
}
},
{
$match: {
archive: {$exists: false},
$or: [
{"referencedOwner.email": {$regex: localQuery}},
{"owner_email": {$regex: localQuery}}]
}
}
]);
Here's a working example on mongoplayground: https://mongoplayground.net/p/NqAvKIgujbm

Populating array of collection which contains references to another collections returns empty array

I have two models Vote and Link,I am trying to populate the votes array in link model,The votes array contains id's that references to the collection Vote,which only contains two fields link and User which also refs to same link model mentioned below and a user model respectively
link Schema:-
const linkSchema = new mongoose.Schema(
{
description: {
type: String,
trim: true,
},
url: {
type: String,
trim: true,
},
postedBy: {
type: mongoose.Types.ObjectId,
ref: "User",
},
votes: [{ type: mongoose.Types.ObjectId, ref: "Vote" }],
},
{
timestamps: true,
}
);
linkSchema.index({ description: "text" });
linkSchema.index({ createdAt: -1 });
module.exports = mongoose.model("Link", linkSchema);
Vote schema:-
const mongoose = require("mongoose");
const voteSchema = new mongoose.Schema({
link: { type: mongoose.Types.ObjectId, ref: "Link" },
user: { type: mongoose.Types.ObjectId, ref: "User" },
});
module.exports = mongoose.model("Vote", voteSchema);
but when i try to get the votes of a link,it always return an empty array ,My function:-
const votes = async ({ id }) => {
const linkData = await Link.findById(id).populate("votes").exec();
console.log(linkData);
};
Output Data:-
{
votes: [], //empty always
_id: 5ecb21059a157117c03d4fac,
url: 'https://www.apollographql.com/docs/react/',
description: 'The best GraphQL client for React',
postedBy: 5ec92a58bf38c32b38400705,
createdAt: 2020-05-25T01:36:05.892Z,
updatedAt: 2020-05-25T01:37:52.266Z,
__v: 0
}
Instead of populate(), you can use aggregate() to get your desired output. This should probably work in your case:
Link.aggregate([
{
$match: {
_id: { $in: [mongoose.Types.ObjectId(id)] // as suggested by the questioner
}
},
{
$lookup: {
from: "vote", // collection to join
localField: "votes", // field from the input documents (filtered after _id is matched)
foreignField: "link", // field to compare with, from other collection
as: "linkData" // output array name
}
}
])
Let me know in the comments.

How to populate in 3 collection in mongoDB with Mongoose

I have three collections such as User, Program, and `Agenda. Those models as follows.
User Model
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
name: {type:String},
email: {type:String}
},{timestamps:true
}
);
module.exports = mongoose.model('User', UserSchema);
Program Model
const mongoose = require('mongoose');
const NoteSchema = mongoose.Schema({
name: {type:String},
timefrom: {type:Date},
timeto: {type:Date},
status: {type:String},
venue: {type:String},
timetype: {type:Number},
userid:{type:mongoose.Schema.Types.ObjectId,ref : 'User', required: true},
logo :{type:String,default: 'programe'}
},{timestamps:true
});
module.exports = mongoose.model('Program', NoteSchema);
Agenda Model
const mongoose = require('mongoose');
const AgendaSchema = mongoose.Schema({
name: {type:String},
timefrom: {type:Date},
timeto: {type:Date},
status: {type:String},
proorder: {type:String},
proid:{type:mongoose.Schema.Types.ObjectId,ref : 'Program', required: true}
},
{timestamps:true}
);
module.exports = mongoose.model('Agenda', AgendaSchema);
Now I only get agenda and program data only.
Agenda Controller
// Retrieve and return all agenda from the database.
exports.findAll = (req, res) => {
Agenda.find()
.populate('proid')
//.populate('userid')
.then(agendas => {
res.send(agendas);
}).catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving agenda."
});
});
};
When I go to this URL and GET method I want to populate agenda document(done), related program document(done) and related user document(this I want)?
The wanted query as like this
SELECT *
FROM users, programs, agendas
WHERE agendas.proid = programs.id AND programs.userid = users.id
You can either use $lookup aggregation
Agenda.aggregate([
{ "$lookup": {
"from": Program.collection.name,
"let": { "proid": "$proid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$proid" ] } } },
{ "$lookup": {
"from": User.collection.name,
"let": { "userid": "$userid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userid" ] } } },
],
"as": "userid"
}},
{ "$unwind": "$userid" }
],
"as": "proid"
}},
{ "$unwind": "$proid" }
])
Or with populate
Agenda.find()
.populate([{ path: 'proid', populate: { path: 'userid' }}])
Both will give you the same result
You can use .populate() and { populate : { path : 'field' } }.
Example:
Agenda.find(). //Collection 1
populate({path:'proid', //Collection 2
populate:{path:'userid' //Collection 3
}})
.exec();
After the 4 collection, you need to identify the model, if it can't be wrong
Example:
Agenda.find(). //Collection 1
populate({path:'proid', //Collection 2
populate:{path:'userid', //Collection 3
populate:{path:'otherFiel', model: Collection //Collection 4 and more
}}})
.exec();
And to finish, if you want get only some fields of some Collection, you can use the propiertie select
Example:
Agenda.find().
populate({path:'proid', select:'name status venue'
populate:{path:'userid'
}})
.exec();

Count the number of occurence of a nested field

I am trying to count the number of documents with a matching criteria in a nested field.
My schema is defined as follows:
let userSchema = new Schema({
//...
interests: [{
type: Schema.Types.ObjectId,
ref: 'Interests'
}],
//...
});
let interestSchema = new Schema({
id: ObjectId,
name: {
type: String,
required: true
},
//...
});
The count must reflect how many times an interest with the same name is choosed by the users.
For example, I must have a result of 2 with the interest 'coding' in the following documents:
{
//Other Fields of user 1
"interests": [
{
"id": "XXX"
"name": "coding"
},
{
"id": "YYY"
"name": "surfing"
}]
}
{
//Other Fields of user 2
"interests": [
{
"id": "ZZZ"
"name": "coding"
}
]
}
I looked into the countDocuments method, but it doesn't seem to allow this kind of count.
EDIT + First solution:
This is how I managed to solve it:
const res = await UserModel.aggregate([
{
$unwind: '$interests'
},
{
$lookup: {
from: "interests",
localField: "interests",
foreignField: "_id",
as: "interests"
}
},
{
$match:{
"interests.name": name
}
},
{
$count: "count"
}
]);
return res[0].count;
The fact that the interests is a referenced type, I can not query for its name unless I pass the lookup stage. I am not sure if this is a good solution regarding performance, since the unwind stage must pass through all the users of the database and create a new element for each of their interests. This why I am not posting it as an answer
To work with elemMatch, I had to change the schema in order to embed the Interest in the User instead of referencing it:
let userSchema = new Schema({
//...
interests: [InterestSchema],
//...
});
let InterestSchema = new Schema({
id: ObjectId,
name: {
type: String,
required: true
},
//...
});
This is how I used the elemMatch:
const count = UserModel
.where('interests').elemMatch( interest => {
interest.where({ name: name });
})
.count();
As I mentioned in my question, the aggregate method works but I am not sure about its performance since I was using a referenced array instead of a sub-document, this is why I had to change the schema of my collection

Mongoose sort and search document by populated field

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

Resources