join collections in mongodb [duplicate] - mongodb

I coming from a sql background so writing queries in sql where I join tables is quite simple but I guess I am missing that in mongoose/mongodb
Basically I know the Subscriber_ID (which maps to a document in the User Collection)
I want to pull the project group, with all the projects that the user belongs to so if I was to write this in pseduo sql it would be like
Select
ProjectGroup.title,
Project.Title
FROM
ProjectGroup,
Project,
User
WHERE
User.id = req.body.subscriber_id
AND Project.subscriber_id = User.id
AND ProjectGroup.project_id = Project.id
There must be a way to do similiar joins in mongoose/mongodb because the type is mapping to a schema right?
My Schemas.....
Project Group Schema
var ProjectGroupSchema = new Schema({
title : String
, projects : [ { type: Schema.Types.ObjectId, ref: 'Project' } ]
});
Project Schema
var ProjectSchema = new Schema({
title : {type : String, default : '', required : true}
, subscribers : [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
User Schema
var UserSchema = new Schema({
first_name : {type: String, required: true}
, last_name : {type: String, required: true}
});
Thank you!

You are just one step away!
Project Group Schema:
var ProjectGroupSchema = new Schema({
title : String
});
Project Schema:
var ProjectSchema = new Schema({
title : {type : String, default : '', required : true},
group : {type: Schema.Types.ObjectId, ref: 'ProjectGroup' },
_users : [{type: Schema.Types.ObjectId, ref: 'User' }]
});
User Schema:
var UserSchema = new Schema({
first_name : {type: String, required: true},
last_name : {type: String, required: true},
subscribing : [{type: Schema.Types.ObjectId, ref: 'Project' }]
});
Then you can do the following:
user.findById(req.userId)
.populate('subscribing')
.exec(function(err, user){
console.log(user.subscribing);
})
Or:
project.find({
subscriber : req.userId
})
.populate('subscriber')
.populate('group')
.exec(function(err, projects){
console.log(projects);
})

There are no joins in Mongodb. This question I think is a good reference:
MongoDB and "joins"
To summarize, different strategies have to be adopted with mongodb for problems that would be addressed via joins in relational DBs. I would say you mainly end-up doing one of these two things:
Embedding: You embed information in a single document that would in a relational DB be distributed amongst different tables.
Joining information client-side: When you need to use information from several places, you query many times and then put the pieces together in your client.

Related

Mongoose Populate with Express, not working in production (Heroku)

This is a MERN app, hosted on github, and it works perfectly on localhost. Unfortunately, it does not work on Heroku.
The issue is the API request, it must return an object and populate an array of OIDs (see Department Model). API request is working. I'm getting the data from MLab, but it doesn't populate... instead returns: "surveys":[]
API File
router.get('/department_data/:d_oid', function(req, res) {
Department.findOne({_id: req.params.d_oid}).populate("surveys").exec(function(err,doc){
if(err) throw(err)
res.send(doc)
})
});
Department Model
**Department Model**
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the survey schema
var departmentSchema = new Schema({
department_name: {
type: String,
trim: true,
required: true
},
surveys: [{
type: Schema.Types.ObjectId,
ref: 'Surveys'
}],
participants: [{
type: String
}],
create_date: {
type: Date,
default: Date.now
},
created_by: {
type: Schema.Types.ObjectId,
ref: 'Created_By'
},
});
departmentSchema.index({ department_name: 1, created_by: 1}, {unique: true});
const Department = mongoose.model('Departments', departmentSchema);
module.exports = Department;
Survey Model
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the survey schema
var surveySchema = new Schema({
survey_name: {
type: String,
trim: true,
required: true
},
questions: [{
type: Schema.Types.ObjectId,
ref: 'Questions'
}],
created_date: {
type: Date,
default: Date.now
}
});
const Survey = mongoose.model('Surveys', surveySchema);
module.exports = Survey;
Solved.
The problem was in the database: the ref OIDs got scrambled with a previous update, so when it was trying to populate, Mongoose couldn't find any matching OIDs.
Solution: we had to purge and re-seed. When the correct OID references exist, this code works as expected in localhost & Heroku.

Using Mongoose Populate Virtuals on Arrays

Here is my Follow model
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
followers: [{
type: Schema.Types.ObjectId,
ref: 'Card'
}],
following: [{
type: Schema.Types.ObjectId,
ref: 'Card'
}]
});
module.exports = mongoose.model('Follow', FollowSchema);
Here's my Card model
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let CardSchema = new Schema({
title: String,
content: String,
likes: Number,
createdById: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Card', CardSchema);
A typical Follow Model in DB will have something like this:
{
"_id" : ObjectId("59f0eef155ee5a5897e1a66d"),
"user" : ObjectId("59e3e617149f0a3f1281e849"),
"following" : [
ObjectId("59e21942ca5652efc4ca30ab"),
ObjectId("59e13b2dca5652efc4ca2cf5")
]
}
Here's what I'm trying to do:
To get all post from those you follow, find user in Follow model, and for each ObjectId in the following array, pull all documents from Card model, matching createdById field.
Using Mongoose Populate Virtuals, how do I do the schema relationship and populate query eventually?
Or, how would I go about creating and querying such a relationship with or without virtuals?

Mongoose why would you use populate over another find?

I'm guessing because you save resources by making 1 request instead of 2 to the database. Is this significant? Should I care to use populate if I'm populating only 1 field (the advantage is clearer when you populate more than 1)?
You don't save resources by using populate. Under the hood mongoose calls the database as many times as required. Consider the example:
module.exports = function(){
var UserSchema = new Schema({
email : {type : String, required: true},
password: {type: String, required: true}
});
return mongoose.model("User", UserSchema);
};
module.exports = function(){
var AccountSchema = new Schema({
number : {type : String, required: true},
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
return mongoose.model("Account", AccountSchema);
};
mongoose.set('debug', true); //to see queries mongoose is using behind the scenes
Account.find({}, function(err, res){
console.log(res)
}).populate("user")
Apart from the results, you'll see something like this on console:
Mongoose: accounts.find({}, { fields: undefined })
Mongoose: users.find({ _id: { '$in': [ ObjectId("5807d6d6aa66d7633a5d7025"), ObjectId("5807d6d6aa66d7633a5d7026"), ObjectId("5807d709aa66d7633a5d7027") ] } }, { fields: undefined })
That's mongoose finding account documents and then user for each one of them.
It's saving you a lot of code and I don't see why you should not use it irrespective of the number of fields you're populating.

Mongoose: does a custom _id need to be declared as an index and be unique

Here is the common way to define a collection structure with Mongoose :
var UserSchema = new Schema({
_id: Schema.Types.ObjectId,
username: String,
...
});
And Now I want _id field declared as Number type :
var UserSchema = new Schema({
_id: Number,
username: String,
...
});
The problem is, do I need to declare more infomation about _id ? Such as :
var UserSchema = new Schema({
_id: {type: Number, required: true, index: {unique: true}},
username: String,
...
});
I am not sure whether MongoDB would do it automatically.
if you know the answer, could you leave a comment below ? Thank you!
Well, after some practice, I realized that, MongoDB would set _id as PRIMARY KEY (NOT NULL + UNIQUE INDEX) automatically. So, just type:
_id: Number,
...

MongoDB $pull value from array of ObjectIDs

I have this document in my collection:
{
"_id" : ObjectId("52718433e18a711923000005"),
"owners" : [
ObjectId("52718433e18a711923000004"),
ObjectId("52ed40dccc5bc50000000003"),
ObjectId("52ed4171abe2780000000003")
]
}
I have the following statement, where I am trying to remove one of the values in owners field:
Business.update({_id:req.body._id}, {$pull:{"owners":req.body.userid}}, function(err){
if(err){
res.json(500, {message:"Could not remove user from admin list"});
}else{
res.json(200);
}
});
I know that req.body._id and req.body.userid have valid values:
{ _id: '52718433e18a711923000005',
userid: '52ed4171abe2780000000003' }
Other operations, such as finding business by ID, etc, work, so it's not an ObjectId format issue. What else could it be?
--
Edit: here is my (abbreviated) schema definition:
var BusinessSchema = new Schema({
business_name: {type: String, required: true},
owners: {type: Array}
});
Your current schema doesn't provide any direction to Mongoose regarding the data type contained within the owners array field. If you want Mongoose to cast your string to an ObjectID you need to provide type information in your schema:
var BusinessSchema = new Schema({
business_name: {type: String, required: true},
owners: [{type: Schema.ObjectId}]
});
It looks like a conversion to ObjectId is required when trying to match values to pull. But not to search. So this works:
var ObjectId = require('mongoose').Types.ObjectId;
Business.update({_id:req.body._id}, {$pull:{"owners": new ObjectId(req.body.userid)}}, function(err){
if(err){
res.json(500, {message:"Could not remove user from admin list"});
}else{
res.json(200);
}
});
--
Edit: So, if I change my schema from owners: {type: Array} to owners: [Schema.Types.ObjectId], I can skip the conversion, and my original statement works.