Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to join two collections in mongoose

I have two Schema defined as below:

var WorksnapsTimeEntry = BaseSchema.extend({
 student: {
     type: Schema.ObjectId,
     ref: 'Student'
 },
 timeEntries: {
     type: Object
 }
 });

var StudentSchema = BaseSchema.extend({
firstName: {
    type: String,
    trim: true,
    default: ''
    // validate: [validateLocalStrategyProperty, 'Please fill in your first name']
},
lastName: {
    type: String,
    trim: true,
    default: ''
    // validate: [validateLocalStrategyProperty, 'Please fill in your last name']
},
displayName: {
    type: String,
    trim: true
},
municipality: {
    type: String
    }
});

And I would like to loop thru each student and show it's time entries. So far I have this code which is obviously not right as I still dont know how do I join WorksnapTimeEntry schema table.

Student.find({ status: 'student' })
        .populate('student')
        .exec(function (err, students) {
            if (err) {
                return res.status(400).send({
                    message: errorHandler.getErrorMessage(err)
                });
            }
            _.forEach(students, function (student) {
               // show student with his time entries....
            });
            res.json(students);
        });

Any one knows how do I achieve such thing?

like image 430
Lulzim Fazlija Avatar asked Apr 23 '16 02:04

Lulzim Fazlija


People also ask

Can we join 2 collections in MongoDB?

For performing MongoDB Join two collections, you must use the $lookup operator. It is defined as a stage that executes a left outer join with another collection and aids in filtering data from joined documents. For example, if a user requires all grades from all students, then the below query can be written: Students.

How do I join a collection in MongoDB?

Join Collections MongoDB is not a relational database, but you can perform a left outer join by using the $lookup stage. The $lookup stage lets you specify which collection you want to join with the current collection, and which fields that should match.

How do I join two databases in MongoDB?

According to the fine manual, createConnection() can be used to connect to multiple databases. However, you need to create separate models for each connection/database: var conn = mongoose. createConnection('mongodb://localhost/testA'); var conn2 = mongoose.

How fetch data from different collections MongoDB?

After having a model, we can use method find() on the model of a particular collection to get documents of the collection. <query>: It is optional. It specifies a selection filter that is used to filter documents using various MongoDB query operators. If not passed, all the documents are returned.


2 Answers

As of version 3.2, you can use $lookup in aggregation pipeline to perform left outer join.

Student.aggregate([{
    $lookup: {
        from: "worksnapsTimeEntries", // collection name in db
        localField: "_id",
        foreignField: "student",
        as: "worksnapsTimeEntries"
    }
}]).exec(function(err, students) {
    // students contain WorksnapsTimeEntries
});
like image 123
Talha Awan Avatar answered Nov 04 '22 08:11

Talha Awan


You don't want .populate() here but instead you want two queries, where the first matches the Student objects to get the _id values, and the second will use $in to match the respective WorksnapsTimeEntry items for those "students".

Using async.waterfall just to avoid some indentation creep:

async.waterfall(
    [
        function(callback) {
          Student.find({ "status": "student" },{ "_id": 1 },callback);
        },
        function(students,callback) {
            WorksnapsTimeEntry.find({
                "student": { "$in": students.map(function(el) {
                    return el._id
                })
            },callback);
        }
    ],
    function(err,results) {
       if (err) {
          // do something
       } else {
          // results are the matching entries
       }
    }
)

If you really must, then you can .populate("student") on the second query to get populated items from the other table.

The reverse case is to query on WorksnapsTimeEntry and return "everything", then filter out any null results from .populate() with a "match" query option:

WorksnapsTimeEntry.find().populate({
    "path": "student",
    "match": { "status": "student" }
}).exec(function(err,entries) {
   // Now client side filter un-matched results
   entries = entries.filter(function(entry) {
       return entry.student != null;
   });
   // Anything not populated by the query condition is now removed
});

So that is not a desirable action, since the "database" is not filtering what is likely the bulk of results.

Unless you have a good reason not to do so, then you probably "should" be "embedding" the data instead. That way the properties like "status" are already available on the collection and additional queries are not required.

If you are using a NoSQL solution like MongoDB you should be embracing it's concepts, rather than sticking to relational design principles. If you are consistently modelling relationally, then you might as well use a relational database, since you won't be getting any benefit from the solution that has other ways to handle that.

like image 14
Blakes Seven Avatar answered Nov 04 '22 06:11

Blakes Seven