简体   繁体   中英

Mongoose - How to aggregate contents from two collections

How can I use a Mongoose aggregate operation for this? Basically, I have two collections - 'Post' and 'Profile'. A user oid called "user" is the common reference between them. I want to use the ID from 'Post' to look up the "handle" element from 'Profile' and aggregate those results, so that the user's handle is included in the response.

Here is how I was able to achieve this when looking up a single post:

// @route   GET api/posts/:id
// @desc    Get post by id
// @access  Public
router.get("/:id", (req, res) => {
  Post.findById(req.params.id)
    .then(post => {
      Profile.findOne({ user: post.user })
        .then(profile => res.json({ ...post._doc, userHandle: profile.handle }));
    })
    .catch(err => res.status(404).json({ "not found": err }));
});

However, when I tried to extend this approach (using a map operation) to the GET that returns ALL of the posts, it became unworkable.

// @route   GET api/posts
// @desc    Get all the posts
// @access  Public
router.get("/", (req, res) => {
  Post.find()
    .sort({ date: -1 })
    .then(posts =>
      posts.map(
        post =>
          Profile.findOne({ user: post.user })
            .then(profile => (
              {
                ...JSON.parse(JSON.stringify(post)),
                userHandle: profile.handle
              }))
      ))
    .then(posts => res.json(posts))
    .catch(err => res.status(404).json({ "none found": err }));
});

In other words, if I have POST:

{
    "_id": {
        "$oid": "5d1523b98d9dd16d832a8c5e"
    },
    "user": {
        "$oid": "5d1504e29dc0bd55461adca7"
    },
    "recipeName": "Pizza",
    "ingredients": "Flour and sauch"
}

and PROFILE:

{
    "_id": {
        "$oid": "5d1505089dc0bd55461adca8"
    },
    "user": {
        "$oid": "5d1504e29dc0bd55461adca7"
    },
    "handle": "jack",
    "status": "Developer"
}

then what I really want is a JSON object like this (when I query a post): (notice 'userHandle' is now present)

{
    "_id": {
        "$oid": "5d1523b98d9dd16d832a8c5e"
    },
    "user": {
        "$oid": "5d1504e29dc0bd55461adca7"
    },
    "recipeName": "Pizza",
    "ingredients": "Flour and sauch",
    "userHandle": "jack"
}

My GET needs to return a whole array of these posts.

Post.find().sort({date: -1}).then((posts) => {
  Post.populate(posts, [{
    path: 'user',
    select: {}, // projection
    model: User // User model
  }], (err, posts) => {
    if (err) return next(err);
    return res.json(posts);
  });
});

Read More About mongoose populate

Edit

If you don't want to include model every time, you can add ref (collection name) in the Schema field, and it will automatically specify the model!

Example:

const postSchema = Schema({
  ...
  user: { type: Schema.Types.ObjectId, ref: 'Users' }
});
getUserDetails: (request, response, next) => {
        try {
            let aggrQuery = [
                {
                    $lookup: {
                        from: "Profiles",
                        localField: "Id",//key name of common id in post collection as a reference
                        foreignField: "profileId",// key name of common id in profile collection
                        as: "userDetails"// handle element as well as other data in profile collection will be stored in userDetails
                    }
                },
                {
                    "$project": {
                       //we have to carry "_id", "ingredients","recipeName","user" of post collection for the next projection,and whole user information is stored in "userDetails"
                        "_id": 1,
                        "ingredients":1,
                        "recipeName":1,
                        "user":1,
                        userDetails : { $arrayElemAt : 
                      ["$userDetails", 0]}
                    }
                },
                {
                    "$project": {
                        //finally we prepare required data set for response,"_id", "ingredients","recipeName","user" of post collection,and only userHandle of Profiles from userDetails array(which contains all profile data)
                        "_id": "$_id",
                        "ingredients":"$ingredients",
                        "recipeName":"$recipeName",
                        "user":"$user",
                        "userDetails":{
                           "userHandle" :"$userDetails.userHandle",
                           "additionField1":"$userDetails.additionField1",
                           "additionField2":"$userDetails.additionField2",
                         }     
                    }
                }
            ]
            Post.aggregate(aggrQuery).exec((err, result) => {
                if(err){
                  response.json({
                      isError: true,
                      statuscode: 404,
                      details: null
                  })
                }
                else{
                   response.json({
                    isError: false,
                    statuscode: 200,
                    details: result
                  })
                }
            })
        }
        catch{
            response.json({
                isError: true,
                statuscode: 404,
                details: null
            });
        }
    },

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM