Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose, sort query by populated field

Tags:

As far as I know, it's possible to sort populated docs with Mongoose (source).

I'm searching for a way to sort a query by one or more populated fields.

Consider this two Mongoose schemas :

var Wizard = new Schema({     name  : { type: String } , spells  : { [{ type: Schema.ObjectId, ref: 'Spell' }] } });  var Spell = new Schema({     name    : { type: String } ,   damages : { type: Number } }); 

Sample JSON:

[{     name: 'Gandalf',     spells: [{             name: 'Fireball',             damages: 20         }] }, {     name: 'Saruman',     spells: [{             name: 'Frozenball',             damages: 10         }] }, {     name: 'Radagast',     spells: [{             name: 'Lightball',             damages: 15         }] }] 

I would like to sort those wizards by their spell damages, using something like :

WizardModel   .find({})   .populate('spells', myfields, myconditions, { sort: [['damages', 'asc']] }) // Should return in the right order: Saruman, Radagast, Gandalf 

I'm actually doing those sorts by hands after querying and would like to optimize that.

like image 237
Adrien Schuler Avatar asked Jul 16 '12 21:07

Adrien Schuler


2 Answers

You can explicitly specify only required parameters of populate method:

WizardModel   .find({})   .populate({path: 'spells', options: { sort: [['damages', 'asc']] }}) 

Have a look at http://mongoosejs.com/docs/api.html#document_Document-populate Here is an example from a link above.

doc .populate('company') .populate({   path: 'notes',   match: /airline/,   select: 'text',   model: 'modelName'   options: opts }, function (err, user) {   assert(doc._id == user._id) // the document itself is passed }) 
like image 165
Artem Fedosov Avatar answered Sep 21 '22 20:09

Artem Fedosov


Even though this is rather an old post, I'd like to share a solution through the MongoDB aggregation lookup pipeline

The important part is this:

 {       $lookup: {         from: 'spells',         localField: 'spells',         foreignField:'_id',         as: 'spells'       }     },     {       $project: {         _id: 1,         name: 1,         // project the values from damages in the spells array in a new array called damages         damages: '$spells.damages',         spells: {           name: 1,           damages: 1         }       }     },     // take the maximum damage from the damages array     {       $project: {         _id: 1,         spells: 1,         name: 1,         maxDamage: {$max: '$damages'}       }     },     // do the sorting     {       $sort: {'maxDamage' : -1}     } 

Find below a complete example

'use strict';  const mongoose = require('mongoose'); const Schema = mongoose.Schema;  mongoose.connect('mongodb://localhost/lotr');  const db = mongoose.connection;  db.on('error', console.error.bind(console, 'connection error:')); db.once('open', () => {      let SpellSchema = new Schema({     name    : { type: String },     damages : { type: Number }   });    let Spell = mongoose.model('Spell', SpellSchema);    let WizardSchema = new Schema({     name: { type: String },     spells: [{ type: Schema.Types.ObjectId, ref: 'Spell' }]   });    let Wizard = mongoose.model('Wizard', WizardSchema);    let fireball = new Spell({     name: 'Fireball',     damages: 20   });    let frozenball = new Spell({     name: 'Frozenball',     damages: 10   });    let lightball = new Spell({     name: 'Lightball',     damages: 15   });    let spells = [fireball, frozenball, lightball];    let wizards = [{     name: 'Gandalf',     spells:[fireball]   }, {      name: 'Saruman',     spells:[frozenball]   }, {     name: 'Radagast',     spells:[lightball]   }];    let aggregation = [     {       $match: {}     },     // find all spells in the spells collection related to wizards and fill populate into wizards.spells     {       $lookup: {         from: 'spells',         localField: 'spells',         foreignField:'_id',         as: 'spells'       }     },     {       $project: {         _id: 1,         name: 1,         // project the values from damages in the spells array in a new array called damages         damages: '$spells.damages',         spells: {           name: 1,           damages: 1         }       }     },     // take the maximum damage from the damages array     {       $project: {         _id: 1,         spells: 1,         name: 1,         maxDamage: {$max: '$damages'}       }     },     // do the sorting     {       $sort: {'maxDamage' : -1}     }   ];   Spell.create(spells, (err, spells) => {     if (err) throw(err);     else {       Wizard.create(wizards, (err, wizards) =>{         if (err) throw(err);         else {           Wizard.aggregate(aggregation)           .exec((err, models) => {             if (err) throw(err);             else {               console.log(models[0]); // eslint-disable-line               console.log(models[1]); // eslint-disable-line               console.log(models[2]); // eslint-disable-line               Wizard.remove().exec(() => {                 Spell.remove().exec(() => {                   process.exit(0);                 });               });             }           });         }       });     }   }); }); 
like image 28
Eydrian Avatar answered Sep 20 '22 20:09

Eydrian