laxman
laxman

Reputation: 1348

Mongoose - query to get data from multiple collections

I want to get query of the mongoose in nodejs application as describe below out put.
user.js, comment.js and post.js are the model files I used.

user.js

var mongoose = require('mongoose');  
var Schema = mongoose.Schema;  
var ObjectId = Schema.ObjectId;  

var userSchema = new Schema({  
        nick_name:{type:String},  
        email: {  
            type: String,  
            trim: true,  
            required: '{PATH} is required!',
            index: true,
        },     
    },{ collection: 'user'});

var User = mongoose.model('User', userSchema);
module.exports = User;  

comment.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

var commentSchema = new Schema({  
         comment: type:String,  
         user_id:{
            type:Schema.Types.ObjectId, ref:'User'
         },  
         is_active :1
},{ collection: 'comment'});

post.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

var postSchema = new Schema({
        post: type:String,
        user_id:{
           type:Schema.Types.ObjectId, ref:'User'
        },
        is_active :1
},{ collection: 'post'});

wants to get out put as follows:

{
 "nick_name":"prakash",
 "email":"[email protected]",
 "comments":[
      {
      "comment":"this is a comment text1",
      "is_active":1,
      },
      {
      "comment":"this is a comment text2",
      "is_active":1,
      }
 ],
 "posts":[
      {
      "post":"this is a post text1",
      "is_active":1,
      },
      {
      "post":"this is a post text2",
      "is_active":1,
      },
      {
      "post":"this is a post text3",
      "is_active":1,
      },
 ]
}

dependencies

"express"  => "version": "4.7.4",
"mongoose" => "version": "4.4.5",
"mongodb"  => "version": "2.4.9",
"OS"  => "ubuntu 14.04 lts 32bit",

if query is not possible ,please suggests me a proper mongoose plugn. but I don't want to any changes in user.js file and its userSchema object.

Upvotes: 25

Views: 79531

Answers (6)

Dhaval Italiya
Dhaval Italiya

Reputation: 449

Use aggregate to do your work in a single query which is almost like a join.

var mongoose = require('mongoose');
var userModel = require('./user');//import user model file
let result = await userModel.aggregate([
  {
    $match: {
      user: mongoose.Types.ObjectId(req.body.user_id),//pass the user id
    }
  },
  {
    $lookup: {
      from: "comments",//your schema name from mongoDB
      localField: "_id", //user_id from user(main) model
      foreignField: "user_id",//user_id from user(sub) model
      pipeline: [ 
        {
          $project:{ //use to select the fileds you want to select
            comment:1, //:1 will select the field
            is_active :1,
            _id:0,//:0 will not select the field
          }
        }
      ],
      as: "comments",//result var name
    }
  },
   {
    $lookup: {
      from: "post",//your schema name from mongoDB
      localField: "_id", //user_id from user(main) model
      foreignField: "user_id",//user_id from user(sub) model
       pipeline: [
        {
          $project:{//use to select the fileds you want to select
            post:1,//:1 will select the field
            is_active :1,
              _id:0,//:0 will not select the field
          }
        }
      ],
      as: "posts",//result var name
    }
  },
  {
    $project:{//use to select the fileds you want to select
      nick_name:1,//:1 will select the field
      email:1,
      _id:0,//:0 will not select the field
      comments:1,
      posts:1
    }
  }
])

Upvotes: 0

Daniel Tkach
Daniel Tkach

Reputation: 736

Adding on to the answers above: What if we only want a few specific fields returned for the populated documents? This can be accomplished by passing the usual field name syntax as the second argument to the populate method:

Story.
  findOne({ title: /casino royale/i }).
  populate('author', 'name'). // only return the Persons name
  exec(function (err, story) {
    if (err) return handleError(err);

    console.log('The author is %s', story.author.name);
    // prints "The author is Ian Fleming"

    console.log('The authors age is %s', story.author.age);
    // prints "The authors age is null"
  });

Reference: https://mongoosejs.com/docs/populate.html#field-selection

Upvotes: 0

J Nguyen
J Nguyen

Reputation: 51

you can consider using populate virtual with both comments and posts like docs (https://mongoosejs.com/docs/populate.html#populate-virtuals) in model User. And using like this: User.find({filter}).populates('virtualComments').populate('virtualPosts')

Upvotes: 0

It is possible .you should use aggregation. it should work. Initiate the variable

    var mongoose = require('mongoose');
    var userCollection = require('./user');//import user model file
    var resources = {
    nick_name: "$nick_name",
    email: "$email"};

    userCollection.aggregate([{
            $group: resources
        }, {
            $lookup: {
                from: "Comments", // collection to join
                localField: "_id",//field from the input documents
                foreignField: "user_id",//field from the documents of the "from" collection
                as: "comments"// output array field
            }
        }, {
            $lookup: {
                from: "Post", // from collection name
                localField: "_id",
                foreignField: "user_id",
                as: "posts"
            }
        }],function (error, data) {
         return res.json(data);
     //handle error case also
});

Upvotes: 20

PigBoT
PigBoT

Reputation: 486

There are no 'joins' in Mongo. But what you would do is change your User Schema to store the ObjectId's of the Comment and Post documents in an array of your User. Then use 'populate' when you need the data with the user.

const userSchema = new Schema({  
    nick_name:{type:String},  
    email: {  
        type: String,  
        trim: true,  
        required: '{PATH} is required!',
        index: true,
    },
    comments: [{ type: Schema.Types.ObjectId, ref:'Comment' }],
    posts: [{ type: Schema.Types.ObjectId, ref:'Post' }]
}, {timestamps: true});

mongoose.model('User', userSchema);

Your query would then look something like this:

User.find()
    .populate('comments posts') // multiple path names in one requires mongoose >= 3.6
    .exec(function(err, usersDocuments) {
        // handle err
        // usersDocuments formatted as desired
    });

Mongoose populate docs

Upvotes: 22

fuelusumar
fuelusumar

Reputation: 797

Of course it is possible, you just have to use populate, let me tell you how:

Import your schemas

var mongoose = require('mongoose');
var userSch = require('userSchema');
var postSch = require('postSchema');
var commSch = require('commentSchema');

Init all the necessary vars

var userModel = mongoose.model('User', userSch);
var postModel = mongoose.model('Post', postSch);
var commModel = mongoose.model('Comment', commSch);

And now, do the query

postModel.find({}).populate('User')
    .exec(function (error, result) {
        return callback(null, null);
    });

commModel.find({}).populate('User')
    .exec(function (error, result) {
        return callback(null, null);
    }); 

This way you get the user inside of your comment and your post, to get the post and comments inside of your user, you have to do 3 queries, one for the user, one for the comments and one for the post, and mix all together

Upvotes: 1

Related Questions