courtyen
courtyen

Reputation: 532

Using mongoose, how do I filter and then group by?

I am using mongoose and so far the query I use gets me all of the critiques based on a docId. I would like to group this result by distinct editors now. Except, my editors is an object.

This is what my critique query looks like:

 Critique.find({docId:req.params.docId}).populate('editor', 'name username').exec(function(err, critiques){
     if(err){
      console.error("Cannot find critiques with docId: " + critiques.docId);   
     }
        console.log(critiques);
        res.jsonp(critiques);
    });

This is my model I am querying:

var CritiqueSchema = new Schema({

    className : String,
    content: String,
    eleId: Number,
    type: String,
    comments: String,  
    isAccepted: Boolean,
    classes: String,
    docId:{
        type: Schema.ObjectId,
        ref: 'Composition'
    },
    created: {
        type: Date,
        default: Date.now
    },
    editor: {
        type: Schema.ObjectId,
        ref: 'User'
    },
});

UPDATE new query:

    Critique.aggregate(
   [ {$match : {docId : mongoose.Types.ObjectId(req.params.docId)}},
     {$group : { _id : "$editor", critiques: { $push: "$$ROOT" } } }
   ]).exec(function(error, result){
          if(!error)console.log(result);
        else console.log(error); 
    });

Upvotes: 0

Views: 2064

Answers (1)

ma08
ma08

Reputation: 3744

What you need is $group in the aggregation framework. But aggregation and population don't go along. So you have two options populate and group the results by yourself by writing a loop or you can use $group to group them and then query each editor manually. The second is better as there will no duplication in editor queries whereas in population there will be significant duplication going on.

    Critique.aggregate(
   [{
      $match:
       {
         docId: ObjectId(req.params.docid)
       }
    }, 
     { $group : { _id : "$editor", critiques: { $push: "$$ROOT" } } }
   ],
    function(err,result){
         if(!err){
             /* result will be of the form:
                 [{_id:<an editor's objectid>,critiques:[{<critique1 document>},{<critique2 document>}...]}...]
             */
             //you will have to manually query for each distinct editor(result[i]._id) which sucks
             //because the call will be asynchronous in the loop and you can't send your response without using async library
             //another option would be to use the $in operator on an array of the distinct critiques: 
             var editors = result.map(function(x) { return x._id } );
             User.find({_id:{$in:editors}},{'username':1},function(err,editorDocs){
                    var editor_ids=editorDocs.map(function(x){return x._id})
                    var index;
                    for(var i=0;i<result.length;i++){
                      index=editor_ids.indexOf(result[i]._id);
                      result[i].editor=editorDocs[index].username;
                    }
                    //result is your final result. In the editor field of each object you will have the username of the editor
              }) 
         }
     })

Check the docs for $$ROOT.

Upvotes: 3

Related Questions