简体   繁体   中英

get a sub document field in $project stage of aggregation

I'm working with the followings schemas:

Question Schema:

var Question = new Schema({
      title: String,
      content: String,
      createdBy: {
          type: Schema.ObjectId,
          ref: 'User',
          required: true
        },
      answers: {
         type: [ { type: Schema.Types.ObjectId, ref: 'Answer' } ]
       }
    });

Answer Schema:

var Answer = new Schema({
     content:String,
     createdBy: {
         type: Schema.Types.ObjectId,
         ref: 'User',
      },
     isBest: {
         type: Boolean,
         default: false
   },
    createdAt: Date,
    votes: Number    
});

I'm trying to do an aggregation where I can have as result the list of all questions with a certain fields like title , createdBy (which is going to be populated after the aggregate) , an answers_count and a has_best field which is going to be true if a question already have a best answer (an answer with the field isBest equals to true) .

I've try with the $project pipeline:

  Question.aggregate([{
  $project: {
    answers_count: { $size: '$answers' },
    title: true,
    createdAt: true,
    createdBy: true,
    has_best_answer: '$answers.isBest'
  }
}, {
  $sort: {
    createdAt: -1
  }
}], function(err, questions){
  if(err){
    return res.status(400).send({ message: err });
  }
  User.populate(questions, { path: 'createdBy', model: 'User', select: 'firstname lastname image' }, function(err, questions){
    return res.json(questions);
  });
});

Also I've try to $unwind the array and then $lookup for answers but no results when doing an answers_count . This is what I've tried with $unwind and $lookup .

Question.aggregate([
  {
    $unwind: '$answers'
  },
  {
    $lookup: {
      from: 'answers',
      localField: 'answers',
      foreignField: '_id',
      as: 'answer'
    }
  },{
    $project: {
      title: true,
      createdBy: true,
      createdAt: true,
      has_best_answer: '$answer.isBest'
    }
  }
], function(err, questions){
  if(err){
    return res.status(400).send({ message: err });
  }
  User.populate(questions, { path: 'createdBy', model: 'User', select:
      'firstname lastname image' }, function(err, questions){
    return res.json(questions);
  });
});

So, because the $unwind in the array, cannot $size in answers array to do an answers_count field, also when I tried to do $group to having uniques questions id like this:

Question.aggregate([
  {
    $unwind: '$answers'
  },
  {
    $lookup: {
      from: 'answers',
      localField: 'answers',
      foreignField: '_id',
      as: 'answer'
    }
  },{
    $project: {
      title: true,
      createdBy: true,
      createdAt: true,
      has_best_answer: '$answer.isBest'
    }
  },
  {
    $group: { _id: '$_id' }
  }
], function(err, questions){
  if(err){
    return res.status(400).send({ message: err });
  }
  User.populate(questions, { path: 'createdBy', model: 'User', select: 'firstname lastname image' }, function(err, questions){
    return res.json(questions);
  });
});

I have this result:

[
   {
      "_id": "5825f2846c7ab9ec004f14ce"
   },
   {
      "_id": "5823b9309de40494239c95cd"
   },
   {
      "_id": "582538366062607c0f4bcdaa"
   },
   {
      "_id": "5855d319b6a475100c7beba2"
   },
   {
    "_id": "5878156328dba3d02052b321"
   }
 ]

This is the output that I'm looking for:

[
   {
      _id: '5825f2846c7ab9ec004f14ce',
      title: 'Some question title',
      createdBy: '5855d319b6a475100c7beba2', // this is going to be       populated,
      createdAt: '2016-11-10T00:02:56.702Z',
      answers_count: 5,
      has_best_answer: true
   },
  {
     _id: '5825f2846c7ab9ec004f14ce',
     title: 'Some other question title',
     createdBy: '5855d319b6a475100c7beba2', // this is going to be populated,
     createdAt: '2016-11-10T00:02:56.702Z',
     answers_count: 2,
     has_best_answer: false
  }
]

You can try something like below.

$lookup - This stage joins all the answers to a question documents.

$project - This stage projects all the required fields. answers_count - Counts the total the number of items in an answers array. has_best_answer - Iterates an answers and compares if any of the isBest field value is true.

Question.aggregate([
  {
    $lookup: {
      from: 'answers',
      localField: 'answers',
      foreignField: '_id',
      as: 'answers'
    }
  },{
    $project: {
      title: true,
      createdBy: true,
      createdAt: true,
      answers_count: { $size: '$answers' },
      has_best_answer: 
        { $anyElementTrue: {
            $map: {
                input: "$answers",
                as: "answer",
                in: { $eq: [ "$$answer.isBest", true] }
            }
        }}
    }
  }
]);

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