Querying after populate in Mongoose












53















I'm pretty new to Mongoose and MongoDB in general so I'm having a difficult time figuring out if something like this is possible:



Item = new Schema({
id: Schema.ObjectId,
dateCreated: { type: Date, default: Date.now },
title: { type: String, default: 'No Title' },
description: { type: String, default: 'No Description' },
tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
});

ItemTag = new Schema({
id: Schema.ObjectId,
tagId: { type: Schema.ObjectId, ref: 'Tag' },
tagName: { type: String }
});



var query = Models.Item.find({});

query
.desc('dateCreated')
.populate('tags')
.where('tags.tagName').in(['funny', 'politics'])
.run(function(err, docs){
// docs is always empty
});


Is there a better way do this?



Edit



Apologies for any confusion. What I'm trying to do is get all Items that contain either the funny tag or politics tag.



Edit



Document without where clause:



[{ 
_id: 4fe90264e5caa33f04000012,
dislikes: 0,
likes: 0,
source: '/uploads/loldog.jpg',
comments: ,
tags: [{
itemId: 4fe90264e5caa33f04000012,
tagName: 'movies',
tagId: 4fe64219007e20e644000007,
_id: 4fe90270e5caa33f04000015,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
},
{
itemId: 4fe90264e5caa33f04000012,
tagName: 'funny',
tagId: 4fe64219007e20e644000002,
_id: 4fe90270e5caa33f04000017,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
}],
viewCount: 0,
rating: 0,
type: 'image',
description: null,
title: 'dogggg',
dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
}, ... ]


With the where clause, I get an empty array.










share|improve this question





























    53















    I'm pretty new to Mongoose and MongoDB in general so I'm having a difficult time figuring out if something like this is possible:



    Item = new Schema({
    id: Schema.ObjectId,
    dateCreated: { type: Date, default: Date.now },
    title: { type: String, default: 'No Title' },
    description: { type: String, default: 'No Description' },
    tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
    });

    ItemTag = new Schema({
    id: Schema.ObjectId,
    tagId: { type: Schema.ObjectId, ref: 'Tag' },
    tagName: { type: String }
    });



    var query = Models.Item.find({});

    query
    .desc('dateCreated')
    .populate('tags')
    .where('tags.tagName').in(['funny', 'politics'])
    .run(function(err, docs){
    // docs is always empty
    });


    Is there a better way do this?



    Edit



    Apologies for any confusion. What I'm trying to do is get all Items that contain either the funny tag or politics tag.



    Edit



    Document without where clause:



    [{ 
    _id: 4fe90264e5caa33f04000012,
    dislikes: 0,
    likes: 0,
    source: '/uploads/loldog.jpg',
    comments: ,
    tags: [{
    itemId: 4fe90264e5caa33f04000012,
    tagName: 'movies',
    tagId: 4fe64219007e20e644000007,
    _id: 4fe90270e5caa33f04000015,
    dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
    rating: 0,
    dislikes: 0,
    likes: 0
    },
    {
    itemId: 4fe90264e5caa33f04000012,
    tagName: 'funny',
    tagId: 4fe64219007e20e644000002,
    _id: 4fe90270e5caa33f04000017,
    dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
    rating: 0,
    dislikes: 0,
    likes: 0
    }],
    viewCount: 0,
    rating: 0,
    type: 'image',
    description: null,
    title: 'dogggg',
    dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
    }, ... ]


    With the where clause, I get an empty array.










    share|improve this question



























      53












      53








      53


      19






      I'm pretty new to Mongoose and MongoDB in general so I'm having a difficult time figuring out if something like this is possible:



      Item = new Schema({
      id: Schema.ObjectId,
      dateCreated: { type: Date, default: Date.now },
      title: { type: String, default: 'No Title' },
      description: { type: String, default: 'No Description' },
      tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
      });

      ItemTag = new Schema({
      id: Schema.ObjectId,
      tagId: { type: Schema.ObjectId, ref: 'Tag' },
      tagName: { type: String }
      });



      var query = Models.Item.find({});

      query
      .desc('dateCreated')
      .populate('tags')
      .where('tags.tagName').in(['funny', 'politics'])
      .run(function(err, docs){
      // docs is always empty
      });


      Is there a better way do this?



      Edit



      Apologies for any confusion. What I'm trying to do is get all Items that contain either the funny tag or politics tag.



      Edit



      Document without where clause:



      [{ 
      _id: 4fe90264e5caa33f04000012,
      dislikes: 0,
      likes: 0,
      source: '/uploads/loldog.jpg',
      comments: ,
      tags: [{
      itemId: 4fe90264e5caa33f04000012,
      tagName: 'movies',
      tagId: 4fe64219007e20e644000007,
      _id: 4fe90270e5caa33f04000015,
      dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
      rating: 0,
      dislikes: 0,
      likes: 0
      },
      {
      itemId: 4fe90264e5caa33f04000012,
      tagName: 'funny',
      tagId: 4fe64219007e20e644000002,
      _id: 4fe90270e5caa33f04000017,
      dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
      rating: 0,
      dislikes: 0,
      likes: 0
      }],
      viewCount: 0,
      rating: 0,
      type: 'image',
      description: null,
      title: 'dogggg',
      dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
      }, ... ]


      With the where clause, I get an empty array.










      share|improve this question
















      I'm pretty new to Mongoose and MongoDB in general so I'm having a difficult time figuring out if something like this is possible:



      Item = new Schema({
      id: Schema.ObjectId,
      dateCreated: { type: Date, default: Date.now },
      title: { type: String, default: 'No Title' },
      description: { type: String, default: 'No Description' },
      tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
      });

      ItemTag = new Schema({
      id: Schema.ObjectId,
      tagId: { type: Schema.ObjectId, ref: 'Tag' },
      tagName: { type: String }
      });



      var query = Models.Item.find({});

      query
      .desc('dateCreated')
      .populate('tags')
      .where('tags.tagName').in(['funny', 'politics'])
      .run(function(err, docs){
      // docs is always empty
      });


      Is there a better way do this?



      Edit



      Apologies for any confusion. What I'm trying to do is get all Items that contain either the funny tag or politics tag.



      Edit



      Document without where clause:



      [{ 
      _id: 4fe90264e5caa33f04000012,
      dislikes: 0,
      likes: 0,
      source: '/uploads/loldog.jpg',
      comments: ,
      tags: [{
      itemId: 4fe90264e5caa33f04000012,
      tagName: 'movies',
      tagId: 4fe64219007e20e644000007,
      _id: 4fe90270e5caa33f04000015,
      dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
      rating: 0,
      dislikes: 0,
      likes: 0
      },
      {
      itemId: 4fe90264e5caa33f04000012,
      tagName: 'funny',
      tagId: 4fe64219007e20e644000002,
      _id: 4fe90270e5caa33f04000017,
      dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
      rating: 0,
      dislikes: 0,
      likes: 0
      }],
      viewCount: 0,
      rating: 0,
      type: 'image',
      description: null,
      title: 'dogggg',
      dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
      }, ... ]


      With the where clause, I get an empty array.







      node.js mongodb mongoose






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jul 3 '12 at 22:45







      jschr

















      asked Jul 3 '12 at 1:19









      jschrjschr

      8561720




      8561720
























          6 Answers
          6






          active

          oldest

          votes


















          25














          With a modern MongoDB greater than 3.2 you can use $lookup as an alternate to .populate() in most cases. This also has the advantage of actually doing the join "on the server" as opposed to what .populate() does which is actually "multiple queries" to "emulate" a join.



          So .populate() is not really a "join" in the sense of how a relational database does it. The $lookup operator on the other hand, actually does the work on the server, and is more or less analogous to a "LEFT JOIN":



          Item.aggregate(
          [
          { "$lookup": {
          "from": ItemTags.collection.name,
          "localField": "tags",
          "foreignField": "_id",
          "as": "tags"
          }},
          { "$unwind": "$tags" },
          { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
          { "$group": {
          "_id": "$_id",
          "dateCreated": { "$first": "$dateCreated" },
          "title": { "$first": "$title" },
          "description": { "$first": "$description" },
          "tags": { "$push": "$tags" }
          }}
          ],
          function(err, result) {
          // "tags" is now filtered by condition and "joined"
          }
          )



          N.B. The .collection.name here actually evaluates to the "string" that is the actual name of the MongoDB collection as assigned to the model. Since mongoose "pluralizes" collection names by default and $lookup needs the actual MongoDB collection name as an argument ( since it's a server operation ), then this is a handy trick to use in mongoose code, as opposed to "hard coding" the collection name directly.




          Whilst we could also use $filter on arrays to remove the unwanted items, this is actually the most efficient form due to Aggregation Pipeline Optimization for the special condition of as $lookup followed by both an $unwind and a $match condition.



          This actually results in the three pipeline stages being rolled into one:



             { "$lookup" : {
          "from" : "itemtags",
          "as" : "tags",
          "localField" : "tags",
          "foreignField" : "_id",
          "unwinding" : {
          "preserveNullAndEmptyArrays" : false
          },
          "matching" : {
          "tagName" : {
          "$in" : [
          "funny",
          "politics"
          ]
          }
          }
          }}


          This is highly optimal as the actual operation "filters the collection to join first", then it returns the results and "unwinds" the array. Both methods are employed so the results do not break the BSON limit of 16MB, which is a constraint that the client does not have.



          The only problem is that it seems "counter-intuitive" in some ways, particularly when you want the results in an array, but that is what the $group is for here, as it reconstructs to the original document form.



          It's also unfortunate that we simply cannot at this time actually write $lookup in the same eventual syntax the server uses. IMHO, this is an oversight to be corrected. But for now, simply using the sequence will work and is the most viable option with the best performance and scalability.



          Addendum - MongoDB 3.6 and upwards



          Though the pattern shown here is fairly optimized due to how the other stages get rolled into the $lookup, it does have one failing in that the "LEFT JOIN" which is normally inherent to both $lookup and the actions of populate() is negated by the "optimal" usage of $unwind here which does not preserve empty arrays. You can add the preserveNullAndEmptyArrays option, but this negates the "optimized" sequence described above and essentially leaves all three stages intact which would normally be combined in the optimization.



          MongoDB 3.6 expands with a "more expressive" form of $lookup allowing a "sub-pipeline" expression. Which not only meets the goal of retaining the "LEFT JOIN" but still allows an optimal query to reduce results returned and with a much simplified syntax:



          Item.aggregate([
          { "$lookup": {
          "from": ItemTags.collection.name,
          "let": { "tags": "$tags" },
          "pipeline": [
          { "$match": {
          "tags": { "$in": [ "politics", "funny" ] },
          "$expr": { "$in": [ "$_id", "$$tags" ] }
          }}
          ]
          }}
          ])


          The $expr used in order to match the declared "local" value with the "foreign" value is actually what MongoDB does "internally" now with the original $lookup syntax. By expressing in this form we can tailor the initial $match expression within the "sub-pipeline" ourselves.



          In fact, as a true "aggregation pipeline" you can do just about anything you can do with an aggregation pipeline within this "sub-pipeline" expression, including "nesting" the levels of $lookup to other related collections.



          Further usage is a bit beyond the scope of what the question here asks, but in relation to even "nested population" then the new usage pattern of $lookup allows this to be much the same, and a "lot" more powerful in it's full usage.





          Working Example



          The following gives an example using a static method on the model. Once that static method is implemented the call simply becomes:



            Item.lookup(
          {
          path: 'tags',
          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
          },
          callback
          )


          Or enhancing to be a bit more modern even becomes:



            let results = await Item.lookup({
          path: 'tags',
          query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
          })


          Making it very similar to .populate() in structure, but it's actually doing the join on the server instead. For completeness, the usage here casts the returned data back to mongoose document instances at according to both the parent and child cases.



          It's fairly trivial and easy to adapt or just use as is for most common cases.




          N.B The use of async here is just for brevity of running the enclosed example. The actual implementation is free of this dependency.




          const async = require('async'),
          mongoose = require('mongoose'),
          Schema = mongoose.Schema;

          mongoose.Promise = global.Promise;
          mongoose.set('debug', true);
          mongoose.connect('mongodb://localhost/looktest');

          const itemTagSchema = new Schema({
          tagName: String
          });

          const itemSchema = new Schema({
          dateCreated: { type: Date, default: Date.now },
          title: String,
          description: String,
          tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
          });

          itemSchema.statics.lookup = function(opt,callback) {
          let rel =
          mongoose.model(this.schema.path(opt.path).caster.options.ref);

          let group = { "$group": { } };
          this.schema.eachPath(p =>
          group.$group[p] = (p === "_id") ? "$_id" :
          (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

          let pipeline = [
          { "$lookup": {
          "from": rel.collection.name,
          "as": opt.path,
          "localField": opt.path,
          "foreignField": "_id"
          }},
          { "$unwind": `$${opt.path}` },
          { "$match": opt.query },
          group
          ];

          this.aggregate(pipeline,(err,result) => {
          if (err) callback(err);
          result = result.map(m => {
          m[opt.path] = m[opt.path].map(r => rel(r));
          return this(m);
          });
          callback(err,result);
          });
          }

          const Item = mongoose.model('Item', itemSchema);
          const ItemTag = mongoose.model('ItemTag', itemTagSchema);

          function log(body) {
          console.log(JSON.stringify(body, undefined, 2))
          }
          async.series(
          [
          // Clean data
          (callback) => async.each(mongoose.models,(model,callback) =>
          model.remove({},callback),callback),

          // Create tags and items
          (callback) =>
          async.waterfall(
          [
          (callback) =>
          ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
          callback),

          (tags, callback) =>
          Item.create({ "title": "Something","description": "An item",
          "tags": tags },callback)
          ],
          callback
          ),

          // Query with our static
          (callback) =>
          Item.lookup(
          {
          path: 'tags',
          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
          },
          callback
          )
          ],
          (err,results) => {
          if (err) throw err;
          let result = results.pop();
          log(result);
          mongoose.disconnect();
          }
          )


          Or a little more modern for Node 8.x and above with async/await and no additional dependencies:



          const { Schema } = mongoose = require('mongoose');
          const uri = 'mongodb://localhost/looktest';

          mongoose.Promise = global.Promise;
          mongoose.set('debug', true);

          const itemTagSchema = new Schema({
          tagName: String
          });

          const itemSchema = new Schema({
          dateCreated: { type: Date, default: Date.now },
          title: String,
          description: String,
          tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
          });

          itemSchema.statics.lookup = function(opt) {
          let rel =
          mongoose.model(this.schema.path(opt.path).caster.options.ref);

          let group = { "$group": { } };
          this.schema.eachPath(p =>
          group.$group[p] = (p === "_id") ? "$_id" :
          (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

          let pipeline = [
          { "$lookup": {
          "from": rel.collection.name,
          "as": opt.path,
          "localField": opt.path,
          "foreignField": "_id"
          }},
          { "$unwind": `$${opt.path}` },
          { "$match": opt.query },
          group
          ];

          return this.aggregate(pipeline).exec().then(r => r.map(m =>
          this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
          ));
          }

          const Item = mongoose.model('Item', itemSchema);
          const ItemTag = mongoose.model('ItemTag', itemTagSchema);

          const log = body => console.log(JSON.stringify(body, undefined, 2));

          (async function() {
          try {

          const conn = await mongoose.connect(uri);

          // Clean data
          await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

          // Create tags and items
          const tags = await ItemTag.create(
          ["movies", "funny"].map(tagName =>({ tagName }))
          );
          const item = await Item.create({
          "title": "Something",
          "description": "An item",
          tags
          });

          // Query with our static
          const result = (await Item.lookup({
          path: 'tags',
          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
          })).pop();
          log(result);

          mongoose.disconnect();

          } catch (e) {
          console.error(e);
          } finally {
          process.exit()
          }
          })()


          And from MongoDB 3.6 and upward, even without the $unwind and $group building:



          const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

          const uri = 'mongodb://localhost/looktest';

          mongoose.Promise = global.Promise;
          mongoose.set('debug', true);

          const itemTagSchema = new Schema({
          tagName: String
          });

          const itemSchema = new Schema({
          title: String,
          description: String,
          tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
          },{ timestamps: true });

          itemSchema.statics.lookup = function({ path, query }) {
          let rel =
          mongoose.model(this.schema.path(path).caster.options.ref);

          // MongoDB 3.6 and up $lookup with sub-pipeline
          let pipeline = [
          { "$lookup": {
          "from": rel.collection.name,
          "as": path,
          "let": { [path]: `$${path}` },
          "pipeline": [
          { "$match": {
          ...query,
          "$expr": { "$in": [ "$_id", `$$${path}` ] }
          }}
          ]
          }}
          ];

          return this.aggregate(pipeline).exec().then(r => r.map(m =>
          this({ ...m, [path]: m[path].map(r => rel(r)) })
          ));
          };

          const Item = mongoose.model('Item', itemSchema);
          const ItemTag = mongoose.model('ItemTag', itemTagSchema);

          const log = body => console.log(JSON.stringify(body, undefined, 2));

          (async function() {

          try {

          const conn = await mongoose.connect(uri);

          // Clean data
          await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

          // Create tags and items
          const tags = await ItemTag.insertMany(
          ["movies", "funny"].map(tagName => ({ tagName }))
          );

          const item = await Item.create({
          "title": "Something",
          "description": "An item",
          tags
          });

          // Query with our static
          let result = (await Item.lookup({
          path: 'tags',
          query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
          })).pop();
          log(result);


          await mongoose.disconnect();

          } catch(e) {
          console.error(e)
          } finally {
          process.exit()
          }

          })()





          share|improve this answer


























          • I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

            – jschr
            May 12 '18 at 2:27













          • I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

            – noitse
            Sep 11 '18 at 15:59



















          37














          what you are asking for isn't directly supported but can be achieved by adding another filter step after the query returns.



          first, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) is definitely what you need to do to filter the tags documents. then, after the query returns you'll need to manually filter out documents that don't have any tags docs that matched the populate criteria. something like:



          query....
          .exec(function(err, docs){
          docs = docs.filter(function(doc){
          return doc.tags.length;
          })
          // do stuff with docs
          });





          share|improve this answer



















          • 1





            Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

            – jschr
            Jul 10 '12 at 18:35






          • 1





            I am curious too....seems the filter is useless if I get null tags back.

            – chovy
            Mar 15 '15 at 0:23











          • @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

            – Aqib Mumtaz
            Nov 11 '15 at 8:58













          • Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

            – Aqib Mumtaz
            Nov 11 '15 at 9:04











          • mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

            – samazi
            Mar 7 '16 at 3:51



















          16














          Try replacing



          .populate('tags').where('tags.tagName').in(['funny', 'politics']) 


          by



          .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )





          share|improve this answer


























          • Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

            – jschr
            Jul 3 '12 at 13:27











          • Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

            – Aafreen Sheikh
            Jul 3 '12 at 14:13











          • Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

            – jschr
            Jul 3 '12 at 22:59











          • Thank you its working and it is very useful.

            – Tigin
            Nov 27 '18 at 17:00



















          13














          Update: Please take a look at the comments - this answer does not correctly match to the question, but maybe it answers other questions of users which came across (I think that because of the upvotes) so I will not delete this "answer":



          First: I know this question is really outdated, but I searched for exactly this problem and this SO post was the Google entry #1. So I implemented the docs.filter version (accepted answer) but as I read in the mongoose v4.6.0 docs we can now simply use:



          Item.find({}).populate({
          path: 'tags',
          match: { tagName: { $in: ['funny', 'politics'] }}
          }).exec((err, items) => {
          console.log(items.tags)
          // contains only tags where tagName is 'funny' or 'politics'
          })


          Hope this helps future search machine users.






          share|improve this answer





















          • 3





            But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

            – OllyBarca
            Dec 1 '16 at 0:10






          • 1





            That is correct, @OllyBarca. According to the docs, match affects the population query only.

            – andreimarinescu
            Dec 7 '16 at 11:52













          • I think this doesn't answer the question

            – Z.Alpha
            Dec 16 '16 at 7:27






          • 1





            @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

            – EnKrypt
            May 8 '18 at 15:15






          • 1





            This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

            – EnKrypt
            May 8 '18 at 15:17



















          1














          @aaronheckmann 's answer worked for me but I had to replace return doc.tags.length; to return doc.tags != null; because that field contain null if it doesn't match with the conditions written inside populate.
          So the final code:



          query....
          .exec(function(err, docs){
          docs = docs.filter(function(doc){
          return doc.tags != null;
          })
          // do stuff with docs
          });





          share|improve this answer
























          • thanks it works for me.

            – Devendra chauhan
            Apr 17 '18 at 8:57



















          0














          After having the same problem myself recently, I've come up with the following solution:



          First, find all ItemTags where tagName is either 'funny' or 'politics' and return an array of ItemTag _ids.



          Then, find Items which contain all ItemTag _ids in the tags array



          ItemTag
          .find({ tagName : { $in : ['funny','politics'] } })
          .lean()
          .distinct('_id')
          .exec((err, itemTagIds) => {
          if (err) { console.error(err); }
          Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
          console.log(items); // Items filtered by tagName
          });
          });





          share|improve this answer























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f11303294%2fquerying-after-populate-in-mongoose%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            6 Answers
            6






            active

            oldest

            votes








            6 Answers
            6






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            25














            With a modern MongoDB greater than 3.2 you can use $lookup as an alternate to .populate() in most cases. This also has the advantage of actually doing the join "on the server" as opposed to what .populate() does which is actually "multiple queries" to "emulate" a join.



            So .populate() is not really a "join" in the sense of how a relational database does it. The $lookup operator on the other hand, actually does the work on the server, and is more or less analogous to a "LEFT JOIN":



            Item.aggregate(
            [
            { "$lookup": {
            "from": ItemTags.collection.name,
            "localField": "tags",
            "foreignField": "_id",
            "as": "tags"
            }},
            { "$unwind": "$tags" },
            { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
            { "$group": {
            "_id": "$_id",
            "dateCreated": { "$first": "$dateCreated" },
            "title": { "$first": "$title" },
            "description": { "$first": "$description" },
            "tags": { "$push": "$tags" }
            }}
            ],
            function(err, result) {
            // "tags" is now filtered by condition and "joined"
            }
            )



            N.B. The .collection.name here actually evaluates to the "string" that is the actual name of the MongoDB collection as assigned to the model. Since mongoose "pluralizes" collection names by default and $lookup needs the actual MongoDB collection name as an argument ( since it's a server operation ), then this is a handy trick to use in mongoose code, as opposed to "hard coding" the collection name directly.




            Whilst we could also use $filter on arrays to remove the unwanted items, this is actually the most efficient form due to Aggregation Pipeline Optimization for the special condition of as $lookup followed by both an $unwind and a $match condition.



            This actually results in the three pipeline stages being rolled into one:



               { "$lookup" : {
            "from" : "itemtags",
            "as" : "tags",
            "localField" : "tags",
            "foreignField" : "_id",
            "unwinding" : {
            "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
            "tagName" : {
            "$in" : [
            "funny",
            "politics"
            ]
            }
            }
            }}


            This is highly optimal as the actual operation "filters the collection to join first", then it returns the results and "unwinds" the array. Both methods are employed so the results do not break the BSON limit of 16MB, which is a constraint that the client does not have.



            The only problem is that it seems "counter-intuitive" in some ways, particularly when you want the results in an array, but that is what the $group is for here, as it reconstructs to the original document form.



            It's also unfortunate that we simply cannot at this time actually write $lookup in the same eventual syntax the server uses. IMHO, this is an oversight to be corrected. But for now, simply using the sequence will work and is the most viable option with the best performance and scalability.



            Addendum - MongoDB 3.6 and upwards



            Though the pattern shown here is fairly optimized due to how the other stages get rolled into the $lookup, it does have one failing in that the "LEFT JOIN" which is normally inherent to both $lookup and the actions of populate() is negated by the "optimal" usage of $unwind here which does not preserve empty arrays. You can add the preserveNullAndEmptyArrays option, but this negates the "optimized" sequence described above and essentially leaves all three stages intact which would normally be combined in the optimization.



            MongoDB 3.6 expands with a "more expressive" form of $lookup allowing a "sub-pipeline" expression. Which not only meets the goal of retaining the "LEFT JOIN" but still allows an optimal query to reduce results returned and with a much simplified syntax:



            Item.aggregate([
            { "$lookup": {
            "from": ItemTags.collection.name,
            "let": { "tags": "$tags" },
            "pipeline": [
            { "$match": {
            "tags": { "$in": [ "politics", "funny" ] },
            "$expr": { "$in": [ "$_id", "$$tags" ] }
            }}
            ]
            }}
            ])


            The $expr used in order to match the declared "local" value with the "foreign" value is actually what MongoDB does "internally" now with the original $lookup syntax. By expressing in this form we can tailor the initial $match expression within the "sub-pipeline" ourselves.



            In fact, as a true "aggregation pipeline" you can do just about anything you can do with an aggregation pipeline within this "sub-pipeline" expression, including "nesting" the levels of $lookup to other related collections.



            Further usage is a bit beyond the scope of what the question here asks, but in relation to even "nested population" then the new usage pattern of $lookup allows this to be much the same, and a "lot" more powerful in it's full usage.





            Working Example



            The following gives an example using a static method on the model. Once that static method is implemented the call simply becomes:



              Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )


            Or enhancing to be a bit more modern even becomes:



              let results = await Item.lookup({
            path: 'tags',
            query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
            })


            Making it very similar to .populate() in structure, but it's actually doing the join on the server instead. For completeness, the usage here casts the returned data back to mongoose document instances at according to both the parent and child cases.



            It's fairly trivial and easy to adapt or just use as is for most common cases.




            N.B The use of async here is just for brevity of running the enclosed example. The actual implementation is free of this dependency.




            const async = require('async'),
            mongoose = require('mongoose'),
            Schema = mongoose.Schema;

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);
            mongoose.connect('mongodb://localhost/looktest');

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt,callback) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            this.aggregate(pipeline,(err,result) => {
            if (err) callback(err);
            result = result.map(m => {
            m[opt.path] = m[opt.path].map(r => rel(r));
            return this(m);
            });
            callback(err,result);
            });
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            function log(body) {
            console.log(JSON.stringify(body, undefined, 2))
            }
            async.series(
            [
            // Clean data
            (callback) => async.each(mongoose.models,(model,callback) =>
            model.remove({},callback),callback),

            // Create tags and items
            (callback) =>
            async.waterfall(
            [
            (callback) =>
            ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
            callback),

            (tags, callback) =>
            Item.create({ "title": "Something","description": "An item",
            "tags": tags },callback)
            ],
            callback
            ),

            // Query with our static
            (callback) =>
            Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )
            ],
            (err,results) => {
            if (err) throw err;
            let result = results.pop();
            log(result);
            mongoose.disconnect();
            }
            )


            Or a little more modern for Node 8.x and above with async/await and no additional dependencies:



            const { Schema } = mongoose = require('mongoose');
            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
            ));
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {
            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.create(
            ["movies", "funny"].map(tagName =>({ tagName }))
            );
            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            const result = (await Item.lookup({
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);

            mongoose.disconnect();

            } catch (e) {
            console.error(e);
            } finally {
            process.exit()
            }
            })()


            And from MongoDB 3.6 and upward, even without the $unwind and $group building:



            const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            },{ timestamps: true });

            itemSchema.statics.lookup = function({ path, query }) {
            let rel =
            mongoose.model(this.schema.path(path).caster.options.ref);

            // MongoDB 3.6 and up $lookup with sub-pipeline
            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": path,
            "let": { [path]: `$${path}` },
            "pipeline": [
            { "$match": {
            ...query,
            "$expr": { "$in": [ "$_id", `$$${path}` ] }
            }}
            ]
            }}
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [path]: m[path].map(r => rel(r)) })
            ));
            };

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {

            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.insertMany(
            ["movies", "funny"].map(tagName => ({ tagName }))
            );

            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            let result = (await Item.lookup({
            path: 'tags',
            query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);


            await mongoose.disconnect();

            } catch(e) {
            console.error(e)
            } finally {
            process.exit()
            }

            })()





            share|improve this answer


























            • I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

              – jschr
              May 12 '18 at 2:27













            • I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

              – noitse
              Sep 11 '18 at 15:59
















            25














            With a modern MongoDB greater than 3.2 you can use $lookup as an alternate to .populate() in most cases. This also has the advantage of actually doing the join "on the server" as opposed to what .populate() does which is actually "multiple queries" to "emulate" a join.



            So .populate() is not really a "join" in the sense of how a relational database does it. The $lookup operator on the other hand, actually does the work on the server, and is more or less analogous to a "LEFT JOIN":



            Item.aggregate(
            [
            { "$lookup": {
            "from": ItemTags.collection.name,
            "localField": "tags",
            "foreignField": "_id",
            "as": "tags"
            }},
            { "$unwind": "$tags" },
            { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
            { "$group": {
            "_id": "$_id",
            "dateCreated": { "$first": "$dateCreated" },
            "title": { "$first": "$title" },
            "description": { "$first": "$description" },
            "tags": { "$push": "$tags" }
            }}
            ],
            function(err, result) {
            // "tags" is now filtered by condition and "joined"
            }
            )



            N.B. The .collection.name here actually evaluates to the "string" that is the actual name of the MongoDB collection as assigned to the model. Since mongoose "pluralizes" collection names by default and $lookup needs the actual MongoDB collection name as an argument ( since it's a server operation ), then this is a handy trick to use in mongoose code, as opposed to "hard coding" the collection name directly.




            Whilst we could also use $filter on arrays to remove the unwanted items, this is actually the most efficient form due to Aggregation Pipeline Optimization for the special condition of as $lookup followed by both an $unwind and a $match condition.



            This actually results in the three pipeline stages being rolled into one:



               { "$lookup" : {
            "from" : "itemtags",
            "as" : "tags",
            "localField" : "tags",
            "foreignField" : "_id",
            "unwinding" : {
            "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
            "tagName" : {
            "$in" : [
            "funny",
            "politics"
            ]
            }
            }
            }}


            This is highly optimal as the actual operation "filters the collection to join first", then it returns the results and "unwinds" the array. Both methods are employed so the results do not break the BSON limit of 16MB, which is a constraint that the client does not have.



            The only problem is that it seems "counter-intuitive" in some ways, particularly when you want the results in an array, but that is what the $group is for here, as it reconstructs to the original document form.



            It's also unfortunate that we simply cannot at this time actually write $lookup in the same eventual syntax the server uses. IMHO, this is an oversight to be corrected. But for now, simply using the sequence will work and is the most viable option with the best performance and scalability.



            Addendum - MongoDB 3.6 and upwards



            Though the pattern shown here is fairly optimized due to how the other stages get rolled into the $lookup, it does have one failing in that the "LEFT JOIN" which is normally inherent to both $lookup and the actions of populate() is negated by the "optimal" usage of $unwind here which does not preserve empty arrays. You can add the preserveNullAndEmptyArrays option, but this negates the "optimized" sequence described above and essentially leaves all three stages intact which would normally be combined in the optimization.



            MongoDB 3.6 expands with a "more expressive" form of $lookup allowing a "sub-pipeline" expression. Which not only meets the goal of retaining the "LEFT JOIN" but still allows an optimal query to reduce results returned and with a much simplified syntax:



            Item.aggregate([
            { "$lookup": {
            "from": ItemTags.collection.name,
            "let": { "tags": "$tags" },
            "pipeline": [
            { "$match": {
            "tags": { "$in": [ "politics", "funny" ] },
            "$expr": { "$in": [ "$_id", "$$tags" ] }
            }}
            ]
            }}
            ])


            The $expr used in order to match the declared "local" value with the "foreign" value is actually what MongoDB does "internally" now with the original $lookup syntax. By expressing in this form we can tailor the initial $match expression within the "sub-pipeline" ourselves.



            In fact, as a true "aggregation pipeline" you can do just about anything you can do with an aggregation pipeline within this "sub-pipeline" expression, including "nesting" the levels of $lookup to other related collections.



            Further usage is a bit beyond the scope of what the question here asks, but in relation to even "nested population" then the new usage pattern of $lookup allows this to be much the same, and a "lot" more powerful in it's full usage.





            Working Example



            The following gives an example using a static method on the model. Once that static method is implemented the call simply becomes:



              Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )


            Or enhancing to be a bit more modern even becomes:



              let results = await Item.lookup({
            path: 'tags',
            query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
            })


            Making it very similar to .populate() in structure, but it's actually doing the join on the server instead. For completeness, the usage here casts the returned data back to mongoose document instances at according to both the parent and child cases.



            It's fairly trivial and easy to adapt or just use as is for most common cases.




            N.B The use of async here is just for brevity of running the enclosed example. The actual implementation is free of this dependency.




            const async = require('async'),
            mongoose = require('mongoose'),
            Schema = mongoose.Schema;

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);
            mongoose.connect('mongodb://localhost/looktest');

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt,callback) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            this.aggregate(pipeline,(err,result) => {
            if (err) callback(err);
            result = result.map(m => {
            m[opt.path] = m[opt.path].map(r => rel(r));
            return this(m);
            });
            callback(err,result);
            });
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            function log(body) {
            console.log(JSON.stringify(body, undefined, 2))
            }
            async.series(
            [
            // Clean data
            (callback) => async.each(mongoose.models,(model,callback) =>
            model.remove({},callback),callback),

            // Create tags and items
            (callback) =>
            async.waterfall(
            [
            (callback) =>
            ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
            callback),

            (tags, callback) =>
            Item.create({ "title": "Something","description": "An item",
            "tags": tags },callback)
            ],
            callback
            ),

            // Query with our static
            (callback) =>
            Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )
            ],
            (err,results) => {
            if (err) throw err;
            let result = results.pop();
            log(result);
            mongoose.disconnect();
            }
            )


            Or a little more modern for Node 8.x and above with async/await and no additional dependencies:



            const { Schema } = mongoose = require('mongoose');
            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
            ));
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {
            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.create(
            ["movies", "funny"].map(tagName =>({ tagName }))
            );
            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            const result = (await Item.lookup({
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);

            mongoose.disconnect();

            } catch (e) {
            console.error(e);
            } finally {
            process.exit()
            }
            })()


            And from MongoDB 3.6 and upward, even without the $unwind and $group building:



            const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            },{ timestamps: true });

            itemSchema.statics.lookup = function({ path, query }) {
            let rel =
            mongoose.model(this.schema.path(path).caster.options.ref);

            // MongoDB 3.6 and up $lookup with sub-pipeline
            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": path,
            "let": { [path]: `$${path}` },
            "pipeline": [
            { "$match": {
            ...query,
            "$expr": { "$in": [ "$_id", `$$${path}` ] }
            }}
            ]
            }}
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [path]: m[path].map(r => rel(r)) })
            ));
            };

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {

            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.insertMany(
            ["movies", "funny"].map(tagName => ({ tagName }))
            );

            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            let result = (await Item.lookup({
            path: 'tags',
            query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);


            await mongoose.disconnect();

            } catch(e) {
            console.error(e)
            } finally {
            process.exit()
            }

            })()





            share|improve this answer


























            • I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

              – jschr
              May 12 '18 at 2:27













            • I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

              – noitse
              Sep 11 '18 at 15:59














            25












            25








            25







            With a modern MongoDB greater than 3.2 you can use $lookup as an alternate to .populate() in most cases. This also has the advantage of actually doing the join "on the server" as opposed to what .populate() does which is actually "multiple queries" to "emulate" a join.



            So .populate() is not really a "join" in the sense of how a relational database does it. The $lookup operator on the other hand, actually does the work on the server, and is more or less analogous to a "LEFT JOIN":



            Item.aggregate(
            [
            { "$lookup": {
            "from": ItemTags.collection.name,
            "localField": "tags",
            "foreignField": "_id",
            "as": "tags"
            }},
            { "$unwind": "$tags" },
            { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
            { "$group": {
            "_id": "$_id",
            "dateCreated": { "$first": "$dateCreated" },
            "title": { "$first": "$title" },
            "description": { "$first": "$description" },
            "tags": { "$push": "$tags" }
            }}
            ],
            function(err, result) {
            // "tags" is now filtered by condition and "joined"
            }
            )



            N.B. The .collection.name here actually evaluates to the "string" that is the actual name of the MongoDB collection as assigned to the model. Since mongoose "pluralizes" collection names by default and $lookup needs the actual MongoDB collection name as an argument ( since it's a server operation ), then this is a handy trick to use in mongoose code, as opposed to "hard coding" the collection name directly.




            Whilst we could also use $filter on arrays to remove the unwanted items, this is actually the most efficient form due to Aggregation Pipeline Optimization for the special condition of as $lookup followed by both an $unwind and a $match condition.



            This actually results in the three pipeline stages being rolled into one:



               { "$lookup" : {
            "from" : "itemtags",
            "as" : "tags",
            "localField" : "tags",
            "foreignField" : "_id",
            "unwinding" : {
            "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
            "tagName" : {
            "$in" : [
            "funny",
            "politics"
            ]
            }
            }
            }}


            This is highly optimal as the actual operation "filters the collection to join first", then it returns the results and "unwinds" the array. Both methods are employed so the results do not break the BSON limit of 16MB, which is a constraint that the client does not have.



            The only problem is that it seems "counter-intuitive" in some ways, particularly when you want the results in an array, but that is what the $group is for here, as it reconstructs to the original document form.



            It's also unfortunate that we simply cannot at this time actually write $lookup in the same eventual syntax the server uses. IMHO, this is an oversight to be corrected. But for now, simply using the sequence will work and is the most viable option with the best performance and scalability.



            Addendum - MongoDB 3.6 and upwards



            Though the pattern shown here is fairly optimized due to how the other stages get rolled into the $lookup, it does have one failing in that the "LEFT JOIN" which is normally inherent to both $lookup and the actions of populate() is negated by the "optimal" usage of $unwind here which does not preserve empty arrays. You can add the preserveNullAndEmptyArrays option, but this negates the "optimized" sequence described above and essentially leaves all three stages intact which would normally be combined in the optimization.



            MongoDB 3.6 expands with a "more expressive" form of $lookup allowing a "sub-pipeline" expression. Which not only meets the goal of retaining the "LEFT JOIN" but still allows an optimal query to reduce results returned and with a much simplified syntax:



            Item.aggregate([
            { "$lookup": {
            "from": ItemTags.collection.name,
            "let": { "tags": "$tags" },
            "pipeline": [
            { "$match": {
            "tags": { "$in": [ "politics", "funny" ] },
            "$expr": { "$in": [ "$_id", "$$tags" ] }
            }}
            ]
            }}
            ])


            The $expr used in order to match the declared "local" value with the "foreign" value is actually what MongoDB does "internally" now with the original $lookup syntax. By expressing in this form we can tailor the initial $match expression within the "sub-pipeline" ourselves.



            In fact, as a true "aggregation pipeline" you can do just about anything you can do with an aggregation pipeline within this "sub-pipeline" expression, including "nesting" the levels of $lookup to other related collections.



            Further usage is a bit beyond the scope of what the question here asks, but in relation to even "nested population" then the new usage pattern of $lookup allows this to be much the same, and a "lot" more powerful in it's full usage.





            Working Example



            The following gives an example using a static method on the model. Once that static method is implemented the call simply becomes:



              Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )


            Or enhancing to be a bit more modern even becomes:



              let results = await Item.lookup({
            path: 'tags',
            query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
            })


            Making it very similar to .populate() in structure, but it's actually doing the join on the server instead. For completeness, the usage here casts the returned data back to mongoose document instances at according to both the parent and child cases.



            It's fairly trivial and easy to adapt or just use as is for most common cases.




            N.B The use of async here is just for brevity of running the enclosed example. The actual implementation is free of this dependency.




            const async = require('async'),
            mongoose = require('mongoose'),
            Schema = mongoose.Schema;

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);
            mongoose.connect('mongodb://localhost/looktest');

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt,callback) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            this.aggregate(pipeline,(err,result) => {
            if (err) callback(err);
            result = result.map(m => {
            m[opt.path] = m[opt.path].map(r => rel(r));
            return this(m);
            });
            callback(err,result);
            });
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            function log(body) {
            console.log(JSON.stringify(body, undefined, 2))
            }
            async.series(
            [
            // Clean data
            (callback) => async.each(mongoose.models,(model,callback) =>
            model.remove({},callback),callback),

            // Create tags and items
            (callback) =>
            async.waterfall(
            [
            (callback) =>
            ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
            callback),

            (tags, callback) =>
            Item.create({ "title": "Something","description": "An item",
            "tags": tags },callback)
            ],
            callback
            ),

            // Query with our static
            (callback) =>
            Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )
            ],
            (err,results) => {
            if (err) throw err;
            let result = results.pop();
            log(result);
            mongoose.disconnect();
            }
            )


            Or a little more modern for Node 8.x and above with async/await and no additional dependencies:



            const { Schema } = mongoose = require('mongoose');
            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
            ));
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {
            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.create(
            ["movies", "funny"].map(tagName =>({ tagName }))
            );
            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            const result = (await Item.lookup({
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);

            mongoose.disconnect();

            } catch (e) {
            console.error(e);
            } finally {
            process.exit()
            }
            })()


            And from MongoDB 3.6 and upward, even without the $unwind and $group building:



            const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            },{ timestamps: true });

            itemSchema.statics.lookup = function({ path, query }) {
            let rel =
            mongoose.model(this.schema.path(path).caster.options.ref);

            // MongoDB 3.6 and up $lookup with sub-pipeline
            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": path,
            "let": { [path]: `$${path}` },
            "pipeline": [
            { "$match": {
            ...query,
            "$expr": { "$in": [ "$_id", `$$${path}` ] }
            }}
            ]
            }}
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [path]: m[path].map(r => rel(r)) })
            ));
            };

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {

            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.insertMany(
            ["movies", "funny"].map(tagName => ({ tagName }))
            );

            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            let result = (await Item.lookup({
            path: 'tags',
            query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);


            await mongoose.disconnect();

            } catch(e) {
            console.error(e)
            } finally {
            process.exit()
            }

            })()





            share|improve this answer















            With a modern MongoDB greater than 3.2 you can use $lookup as an alternate to .populate() in most cases. This also has the advantage of actually doing the join "on the server" as opposed to what .populate() does which is actually "multiple queries" to "emulate" a join.



            So .populate() is not really a "join" in the sense of how a relational database does it. The $lookup operator on the other hand, actually does the work on the server, and is more or less analogous to a "LEFT JOIN":



            Item.aggregate(
            [
            { "$lookup": {
            "from": ItemTags.collection.name,
            "localField": "tags",
            "foreignField": "_id",
            "as": "tags"
            }},
            { "$unwind": "$tags" },
            { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
            { "$group": {
            "_id": "$_id",
            "dateCreated": { "$first": "$dateCreated" },
            "title": { "$first": "$title" },
            "description": { "$first": "$description" },
            "tags": { "$push": "$tags" }
            }}
            ],
            function(err, result) {
            // "tags" is now filtered by condition and "joined"
            }
            )



            N.B. The .collection.name here actually evaluates to the "string" that is the actual name of the MongoDB collection as assigned to the model. Since mongoose "pluralizes" collection names by default and $lookup needs the actual MongoDB collection name as an argument ( since it's a server operation ), then this is a handy trick to use in mongoose code, as opposed to "hard coding" the collection name directly.




            Whilst we could also use $filter on arrays to remove the unwanted items, this is actually the most efficient form due to Aggregation Pipeline Optimization for the special condition of as $lookup followed by both an $unwind and a $match condition.



            This actually results in the three pipeline stages being rolled into one:



               { "$lookup" : {
            "from" : "itemtags",
            "as" : "tags",
            "localField" : "tags",
            "foreignField" : "_id",
            "unwinding" : {
            "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
            "tagName" : {
            "$in" : [
            "funny",
            "politics"
            ]
            }
            }
            }}


            This is highly optimal as the actual operation "filters the collection to join first", then it returns the results and "unwinds" the array. Both methods are employed so the results do not break the BSON limit of 16MB, which is a constraint that the client does not have.



            The only problem is that it seems "counter-intuitive" in some ways, particularly when you want the results in an array, but that is what the $group is for here, as it reconstructs to the original document form.



            It's also unfortunate that we simply cannot at this time actually write $lookup in the same eventual syntax the server uses. IMHO, this is an oversight to be corrected. But for now, simply using the sequence will work and is the most viable option with the best performance and scalability.



            Addendum - MongoDB 3.6 and upwards



            Though the pattern shown here is fairly optimized due to how the other stages get rolled into the $lookup, it does have one failing in that the "LEFT JOIN" which is normally inherent to both $lookup and the actions of populate() is negated by the "optimal" usage of $unwind here which does not preserve empty arrays. You can add the preserveNullAndEmptyArrays option, but this negates the "optimized" sequence described above and essentially leaves all three stages intact which would normally be combined in the optimization.



            MongoDB 3.6 expands with a "more expressive" form of $lookup allowing a "sub-pipeline" expression. Which not only meets the goal of retaining the "LEFT JOIN" but still allows an optimal query to reduce results returned and with a much simplified syntax:



            Item.aggregate([
            { "$lookup": {
            "from": ItemTags.collection.name,
            "let": { "tags": "$tags" },
            "pipeline": [
            { "$match": {
            "tags": { "$in": [ "politics", "funny" ] },
            "$expr": { "$in": [ "$_id", "$$tags" ] }
            }}
            ]
            }}
            ])


            The $expr used in order to match the declared "local" value with the "foreign" value is actually what MongoDB does "internally" now with the original $lookup syntax. By expressing in this form we can tailor the initial $match expression within the "sub-pipeline" ourselves.



            In fact, as a true "aggregation pipeline" you can do just about anything you can do with an aggregation pipeline within this "sub-pipeline" expression, including "nesting" the levels of $lookup to other related collections.



            Further usage is a bit beyond the scope of what the question here asks, but in relation to even "nested population" then the new usage pattern of $lookup allows this to be much the same, and a "lot" more powerful in it's full usage.





            Working Example



            The following gives an example using a static method on the model. Once that static method is implemented the call simply becomes:



              Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )


            Or enhancing to be a bit more modern even becomes:



              let results = await Item.lookup({
            path: 'tags',
            query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
            })


            Making it very similar to .populate() in structure, but it's actually doing the join on the server instead. For completeness, the usage here casts the returned data back to mongoose document instances at according to both the parent and child cases.



            It's fairly trivial and easy to adapt or just use as is for most common cases.




            N.B The use of async here is just for brevity of running the enclosed example. The actual implementation is free of this dependency.




            const async = require('async'),
            mongoose = require('mongoose'),
            Schema = mongoose.Schema;

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);
            mongoose.connect('mongodb://localhost/looktest');

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt,callback) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            this.aggregate(pipeline,(err,result) => {
            if (err) callback(err);
            result = result.map(m => {
            m[opt.path] = m[opt.path].map(r => rel(r));
            return this(m);
            });
            callback(err,result);
            });
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            function log(body) {
            console.log(JSON.stringify(body, undefined, 2))
            }
            async.series(
            [
            // Clean data
            (callback) => async.each(mongoose.models,(model,callback) =>
            model.remove({},callback),callback),

            // Create tags and items
            (callback) =>
            async.waterfall(
            [
            (callback) =>
            ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
            callback),

            (tags, callback) =>
            Item.create({ "title": "Something","description": "An item",
            "tags": tags },callback)
            ],
            callback
            ),

            // Query with our static
            (callback) =>
            Item.lookup(
            {
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            },
            callback
            )
            ],
            (err,results) => {
            if (err) throw err;
            let result = results.pop();
            log(result);
            mongoose.disconnect();
            }
            )


            Or a little more modern for Node 8.x and above with async/await and no additional dependencies:



            const { Schema } = mongoose = require('mongoose');
            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            dateCreated: { type: Date, default: Date.now },
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            });

            itemSchema.statics.lookup = function(opt) {
            let rel =
            mongoose.model(this.schema.path(opt.path).caster.options.ref);

            let group = { "$group": { } };
            this.schema.eachPath(p =>
            group.$group[p] = (p === "_id") ? "$_id" :
            (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": opt.path,
            "localField": opt.path,
            "foreignField": "_id"
            }},
            { "$unwind": `$${opt.path}` },
            { "$match": opt.query },
            group
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
            ));
            }

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {
            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.create(
            ["movies", "funny"].map(tagName =>({ tagName }))
            );
            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            const result = (await Item.lookup({
            path: 'tags',
            query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);

            mongoose.disconnect();

            } catch (e) {
            console.error(e);
            } finally {
            process.exit()
            }
            })()


            And from MongoDB 3.6 and upward, even without the $unwind and $group building:



            const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

            const uri = 'mongodb://localhost/looktest';

            mongoose.Promise = global.Promise;
            mongoose.set('debug', true);

            const itemTagSchema = new Schema({
            tagName: String
            });

            const itemSchema = new Schema({
            title: String,
            description: String,
            tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
            },{ timestamps: true });

            itemSchema.statics.lookup = function({ path, query }) {
            let rel =
            mongoose.model(this.schema.path(path).caster.options.ref);

            // MongoDB 3.6 and up $lookup with sub-pipeline
            let pipeline = [
            { "$lookup": {
            "from": rel.collection.name,
            "as": path,
            "let": { [path]: `$${path}` },
            "pipeline": [
            { "$match": {
            ...query,
            "$expr": { "$in": [ "$_id", `$$${path}` ] }
            }}
            ]
            }}
            ];

            return this.aggregate(pipeline).exec().then(r => r.map(m =>
            this({ ...m, [path]: m[path].map(r => rel(r)) })
            ));
            };

            const Item = mongoose.model('Item', itemSchema);
            const ItemTag = mongoose.model('ItemTag', itemTagSchema);

            const log = body => console.log(JSON.stringify(body, undefined, 2));

            (async function() {

            try {

            const conn = await mongoose.connect(uri);

            // Clean data
            await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

            // Create tags and items
            const tags = await ItemTag.insertMany(
            ["movies", "funny"].map(tagName => ({ tagName }))
            );

            const item = await Item.create({
            "title": "Something",
            "description": "An item",
            tags
            });

            // Query with our static
            let result = (await Item.lookup({
            path: 'tags',
            query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
            })).pop();
            log(result);


            await mongoose.disconnect();

            } catch(e) {
            console.error(e)
            } finally {
            process.exit()
            }

            })()






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited May 12 '18 at 3:57

























            answered Jun 22 '17 at 0:51









            Neil LunnNeil Lunn

            97.8k23174184




            97.8k23174184













            • I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

              – jschr
              May 12 '18 at 2:27













            • I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

              – noitse
              Sep 11 '18 at 15:59



















            • I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

              – jschr
              May 12 '18 at 2:27













            • I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

              – noitse
              Sep 11 '18 at 15:59

















            I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

            – jschr
            May 12 '18 at 2:27







            I'm no longer using Mongo / Mongoose but I've accepted your answer since this is a popular question and looks like this has been helpful to others. Glad to see this problem has a more scalable solution now. Thanks for providing an updated answer.

            – jschr
            May 12 '18 at 2:27















            I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

            – noitse
            Sep 11 '18 at 15:59





            I know a lot of time passed since this answer. But one question. How do reference some collection that is under discriminator? Since if I reference eg 'AllData' collection, it will search all fields that match, instead of searching inside only 'UserConnection' for eg. Since AllData contains all documents, with _type attached

            – noitse
            Sep 11 '18 at 15:59













            37














            what you are asking for isn't directly supported but can be achieved by adding another filter step after the query returns.



            first, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) is definitely what you need to do to filter the tags documents. then, after the query returns you'll need to manually filter out documents that don't have any tags docs that matched the populate criteria. something like:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags.length;
            })
            // do stuff with docs
            });





            share|improve this answer



















            • 1





              Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

              – jschr
              Jul 10 '12 at 18:35






            • 1





              I am curious too....seems the filter is useless if I get null tags back.

              – chovy
              Mar 15 '15 at 0:23











            • @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

              – Aqib Mumtaz
              Nov 11 '15 at 8:58













            • Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

              – Aqib Mumtaz
              Nov 11 '15 at 9:04











            • mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

              – samazi
              Mar 7 '16 at 3:51
















            37














            what you are asking for isn't directly supported but can be achieved by adding another filter step after the query returns.



            first, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) is definitely what you need to do to filter the tags documents. then, after the query returns you'll need to manually filter out documents that don't have any tags docs that matched the populate criteria. something like:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags.length;
            })
            // do stuff with docs
            });





            share|improve this answer



















            • 1





              Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

              – jschr
              Jul 10 '12 at 18:35






            • 1





              I am curious too....seems the filter is useless if I get null tags back.

              – chovy
              Mar 15 '15 at 0:23











            • @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

              – Aqib Mumtaz
              Nov 11 '15 at 8:58













            • Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

              – Aqib Mumtaz
              Nov 11 '15 at 9:04











            • mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

              – samazi
              Mar 7 '16 at 3:51














            37












            37








            37







            what you are asking for isn't directly supported but can be achieved by adding another filter step after the query returns.



            first, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) is definitely what you need to do to filter the tags documents. then, after the query returns you'll need to manually filter out documents that don't have any tags docs that matched the populate criteria. something like:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags.length;
            })
            // do stuff with docs
            });





            share|improve this answer













            what you are asking for isn't directly supported but can be achieved by adding another filter step after the query returns.



            first, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) is definitely what you need to do to filter the tags documents. then, after the query returns you'll need to manually filter out documents that don't have any tags docs that matched the populate criteria. something like:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags.length;
            })
            // do stuff with docs
            });






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Jul 10 '12 at 17:16









            aaronheckmannaaronheckmann

            8,05623029




            8,05623029








            • 1





              Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

              – jschr
              Jul 10 '12 at 18:35






            • 1





              I am curious too....seems the filter is useless if I get null tags back.

              – chovy
              Mar 15 '15 at 0:23











            • @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

              – Aqib Mumtaz
              Nov 11 '15 at 8:58













            • Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

              – Aqib Mumtaz
              Nov 11 '15 at 9:04











            • mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

              – samazi
              Mar 7 '16 at 3:51














            • 1





              Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

              – jschr
              Jul 10 '12 at 18:35






            • 1





              I am curious too....seems the filter is useless if I get null tags back.

              – chovy
              Mar 15 '15 at 0:23











            • @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

              – Aqib Mumtaz
              Nov 11 '15 at 8:58













            • Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

              – Aqib Mumtaz
              Nov 11 '15 at 9:04











            • mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

              – samazi
              Mar 7 '16 at 3:51








            1




            1





            Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

            – jschr
            Jul 10 '12 at 18:35





            Hey Aaron, thanks for the reply. I may be wrong but won't the $in on populate() only populate the matched tags? So any additional tags on the item will get filtered out. It sounds like I'll have to populate all items and have the second filter step reduce it based on tag name then.

            – jschr
            Jul 10 '12 at 18:35




            1




            1





            I am curious too....seems the filter is useless if I get null tags back.

            – chovy
            Mar 15 '15 at 0:23





            I am curious too....seems the filter is useless if I get null tags back.

            – chovy
            Mar 15 '15 at 0:23













            @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

            – Aqib Mumtaz
            Nov 11 '15 at 8:58







            @aaronheckmann I have implemented your suggested solution, you are right about to do filter after .exec, because though populate query is populating only required objects but still its returning entire data set. Do you think in Newer version of Mongoose there is some option to return only populated data set so we don't need to go for another filtering?

            – Aqib Mumtaz
            Nov 11 '15 at 8:58















            Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

            – Aqib Mumtaz
            Nov 11 '15 at 9:04





            Im also curious to know about the performance, If query is returning entire dataset at the end then there is no purpose of going for population filtering? What do you say? Im adapting population query for performance optimisation but this way performance wont get better for large dataset?

            – Aqib Mumtaz
            Nov 11 '15 at 9:04













            mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

            – samazi
            Mar 7 '16 at 3:51





            mongoosejs.com/docs/api.html#query_Query-populate has all the details if anyone else is interested

            – samazi
            Mar 7 '16 at 3:51











            16














            Try replacing



            .populate('tags').where('tags.tagName').in(['funny', 'politics']) 


            by



            .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )





            share|improve this answer


























            • Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

              – jschr
              Jul 3 '12 at 13:27











            • Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

              – Aafreen Sheikh
              Jul 3 '12 at 14:13











            • Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

              – jschr
              Jul 3 '12 at 22:59











            • Thank you its working and it is very useful.

              – Tigin
              Nov 27 '18 at 17:00
















            16














            Try replacing



            .populate('tags').where('tags.tagName').in(['funny', 'politics']) 


            by



            .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )





            share|improve this answer


























            • Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

              – jschr
              Jul 3 '12 at 13:27











            • Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

              – Aafreen Sheikh
              Jul 3 '12 at 14:13











            • Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

              – jschr
              Jul 3 '12 at 22:59











            • Thank you its working and it is very useful.

              – Tigin
              Nov 27 '18 at 17:00














            16












            16








            16







            Try replacing



            .populate('tags').where('tags.tagName').in(['funny', 'politics']) 


            by



            .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )





            share|improve this answer















            Try replacing



            .populate('tags').where('tags.tagName').in(['funny', 'politics']) 


            by



            .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jul 3 '12 at 19:46









            Sergio Tulentsev

            180k30289305




            180k30289305










            answered Jul 3 '12 at 6:57









            Aafreen SheikhAafreen Sheikh

            2,05752639




            2,05752639













            • Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

              – jschr
              Jul 3 '12 at 13:27











            • Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

              – Aafreen Sheikh
              Jul 3 '12 at 14:13











            • Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

              – jschr
              Jul 3 '12 at 22:59











            • Thank you its working and it is very useful.

              – Tigin
              Nov 27 '18 at 17:00



















            • Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

              – jschr
              Jul 3 '12 at 13:27











            • Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

              – Aafreen Sheikh
              Jul 3 '12 at 14:13











            • Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

              – jschr
              Jul 3 '12 at 22:59











            • Thank you its working and it is very useful.

              – Tigin
              Nov 27 '18 at 17:00

















            Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

            – jschr
            Jul 3 '12 at 13:27





            Thanks for the reply. I believe what this does is only populate each item with funny or politics, which wouldn't reduce the parent list. What I would actually like is only items that have funny or politics in their tag.

            – jschr
            Jul 3 '12 at 13:27













            Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

            – Aafreen Sheikh
            Jul 3 '12 at 14:13





            Can you show how your document looks like? Coz a 'where' inside the tags array seems like a valid operation to me..Are we just getting the syntax wrong..Have you tried removing that 'where' clause completely and checked if anything is returned? Alternatively, just to test if writing 'tags.tagName' is syntactically ok, you might forget the ref thing for a while and try out your query with an embedded array inside the 'Item' document.

            – Aafreen Sheikh
            Jul 3 '12 at 14:13













            Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

            – jschr
            Jul 3 '12 at 22:59





            Edited my original post with the document. I was able to test it with the model as an embedded array inside Item with success but unfortunately I require it to be a DBRef as ItemTag is frequently updated. Thanks again for the help.

            – jschr
            Jul 3 '12 at 22:59













            Thank you its working and it is very useful.

            – Tigin
            Nov 27 '18 at 17:00





            Thank you its working and it is very useful.

            – Tigin
            Nov 27 '18 at 17:00











            13














            Update: Please take a look at the comments - this answer does not correctly match to the question, but maybe it answers other questions of users which came across (I think that because of the upvotes) so I will not delete this "answer":



            First: I know this question is really outdated, but I searched for exactly this problem and this SO post was the Google entry #1. So I implemented the docs.filter version (accepted answer) but as I read in the mongoose v4.6.0 docs we can now simply use:



            Item.find({}).populate({
            path: 'tags',
            match: { tagName: { $in: ['funny', 'politics'] }}
            }).exec((err, items) => {
            console.log(items.tags)
            // contains only tags where tagName is 'funny' or 'politics'
            })


            Hope this helps future search machine users.






            share|improve this answer





















            • 3





              But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

              – OllyBarca
              Dec 1 '16 at 0:10






            • 1





              That is correct, @OllyBarca. According to the docs, match affects the population query only.

              – andreimarinescu
              Dec 7 '16 at 11:52













            • I think this doesn't answer the question

              – Z.Alpha
              Dec 16 '16 at 7:27






            • 1





              @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

              – EnKrypt
              May 8 '18 at 15:15






            • 1





              This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

              – EnKrypt
              May 8 '18 at 15:17
















            13














            Update: Please take a look at the comments - this answer does not correctly match to the question, but maybe it answers other questions of users which came across (I think that because of the upvotes) so I will not delete this "answer":



            First: I know this question is really outdated, but I searched for exactly this problem and this SO post was the Google entry #1. So I implemented the docs.filter version (accepted answer) but as I read in the mongoose v4.6.0 docs we can now simply use:



            Item.find({}).populate({
            path: 'tags',
            match: { tagName: { $in: ['funny', 'politics'] }}
            }).exec((err, items) => {
            console.log(items.tags)
            // contains only tags where tagName is 'funny' or 'politics'
            })


            Hope this helps future search machine users.






            share|improve this answer





















            • 3





              But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

              – OllyBarca
              Dec 1 '16 at 0:10






            • 1





              That is correct, @OllyBarca. According to the docs, match affects the population query only.

              – andreimarinescu
              Dec 7 '16 at 11:52













            • I think this doesn't answer the question

              – Z.Alpha
              Dec 16 '16 at 7:27






            • 1





              @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

              – EnKrypt
              May 8 '18 at 15:15






            • 1





              This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

              – EnKrypt
              May 8 '18 at 15:17














            13












            13








            13







            Update: Please take a look at the comments - this answer does not correctly match to the question, but maybe it answers other questions of users which came across (I think that because of the upvotes) so I will not delete this "answer":



            First: I know this question is really outdated, but I searched for exactly this problem and this SO post was the Google entry #1. So I implemented the docs.filter version (accepted answer) but as I read in the mongoose v4.6.0 docs we can now simply use:



            Item.find({}).populate({
            path: 'tags',
            match: { tagName: { $in: ['funny', 'politics'] }}
            }).exec((err, items) => {
            console.log(items.tags)
            // contains only tags where tagName is 'funny' or 'politics'
            })


            Hope this helps future search machine users.






            share|improve this answer















            Update: Please take a look at the comments - this answer does not correctly match to the question, but maybe it answers other questions of users which came across (I think that because of the upvotes) so I will not delete this "answer":



            First: I know this question is really outdated, but I searched for exactly this problem and this SO post was the Google entry #1. So I implemented the docs.filter version (accepted answer) but as I read in the mongoose v4.6.0 docs we can now simply use:



            Item.find({}).populate({
            path: 'tags',
            match: { tagName: { $in: ['funny', 'politics'] }}
            }).exec((err, items) => {
            console.log(items.tags)
            // contains only tags where tagName is 'funny' or 'politics'
            })


            Hope this helps future search machine users.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited May 8 '18 at 17:04

























            answered Sep 16 '16 at 18:26









            FabianFabian

            1,0701935




            1,0701935








            • 3





              But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

              – OllyBarca
              Dec 1 '16 at 0:10






            • 1





              That is correct, @OllyBarca. According to the docs, match affects the population query only.

              – andreimarinescu
              Dec 7 '16 at 11:52













            • I think this doesn't answer the question

              – Z.Alpha
              Dec 16 '16 at 7:27






            • 1





              @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

              – EnKrypt
              May 8 '18 at 15:15






            • 1





              This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

              – EnKrypt
              May 8 '18 at 15:17














            • 3





              But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

              – OllyBarca
              Dec 1 '16 at 0:10






            • 1





              That is correct, @OllyBarca. According to the docs, match affects the population query only.

              – andreimarinescu
              Dec 7 '16 at 11:52













            • I think this doesn't answer the question

              – Z.Alpha
              Dec 16 '16 at 7:27






            • 1





              @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

              – EnKrypt
              May 8 '18 at 15:15






            • 1





              This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

              – EnKrypt
              May 8 '18 at 15:17








            3




            3





            But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

            – OllyBarca
            Dec 1 '16 at 0:10





            But this will only filter the items.tags array surely? Items will be returned regardless of the tagName...

            – OllyBarca
            Dec 1 '16 at 0:10




            1




            1





            That is correct, @OllyBarca. According to the docs, match affects the population query only.

            – andreimarinescu
            Dec 7 '16 at 11:52







            That is correct, @OllyBarca. According to the docs, match affects the population query only.

            – andreimarinescu
            Dec 7 '16 at 11:52















            I think this doesn't answer the question

            – Z.Alpha
            Dec 16 '16 at 7:27





            I think this doesn't answer the question

            – Z.Alpha
            Dec 16 '16 at 7:27




            1




            1





            @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

            – EnKrypt
            May 8 '18 at 15:15





            @Fabian that is not an error. Only the population query (in this case fans) gets filtered. The actual document returned (which is Story, contains fans as a property) is not affected or filtered.

            – EnKrypt
            May 8 '18 at 15:15




            1




            1





            This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

            – EnKrypt
            May 8 '18 at 15:17





            This answer is thus not correct, for the reasons mentioned in the comments. Anyone looking at this in the future should be careful.

            – EnKrypt
            May 8 '18 at 15:17











            1














            @aaronheckmann 's answer worked for me but I had to replace return doc.tags.length; to return doc.tags != null; because that field contain null if it doesn't match with the conditions written inside populate.
            So the final code:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags != null;
            })
            // do stuff with docs
            });





            share|improve this answer
























            • thanks it works for me.

              – Devendra chauhan
              Apr 17 '18 at 8:57
















            1














            @aaronheckmann 's answer worked for me but I had to replace return doc.tags.length; to return doc.tags != null; because that field contain null if it doesn't match with the conditions written inside populate.
            So the final code:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags != null;
            })
            // do stuff with docs
            });





            share|improve this answer
























            • thanks it works for me.

              – Devendra chauhan
              Apr 17 '18 at 8:57














            1












            1








            1







            @aaronheckmann 's answer worked for me but I had to replace return doc.tags.length; to return doc.tags != null; because that field contain null if it doesn't match with the conditions written inside populate.
            So the final code:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags != null;
            })
            // do stuff with docs
            });





            share|improve this answer













            @aaronheckmann 's answer worked for me but I had to replace return doc.tags.length; to return doc.tags != null; because that field contain null if it doesn't match with the conditions written inside populate.
            So the final code:



            query....
            .exec(function(err, docs){
            docs = docs.filter(function(doc){
            return doc.tags != null;
            })
            // do stuff with docs
            });






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Sep 27 '17 at 21:47









            HernanFilaHernanFila

            565




            565













            • thanks it works for me.

              – Devendra chauhan
              Apr 17 '18 at 8:57



















            • thanks it works for me.

              – Devendra chauhan
              Apr 17 '18 at 8:57

















            thanks it works for me.

            – Devendra chauhan
            Apr 17 '18 at 8:57





            thanks it works for me.

            – Devendra chauhan
            Apr 17 '18 at 8:57











            0














            After having the same problem myself recently, I've come up with the following solution:



            First, find all ItemTags where tagName is either 'funny' or 'politics' and return an array of ItemTag _ids.



            Then, find Items which contain all ItemTag _ids in the tags array



            ItemTag
            .find({ tagName : { $in : ['funny','politics'] } })
            .lean()
            .distinct('_id')
            .exec((err, itemTagIds) => {
            if (err) { console.error(err); }
            Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
            console.log(items); // Items filtered by tagName
            });
            });





            share|improve this answer




























              0














              After having the same problem myself recently, I've come up with the following solution:



              First, find all ItemTags where tagName is either 'funny' or 'politics' and return an array of ItemTag _ids.



              Then, find Items which contain all ItemTag _ids in the tags array



              ItemTag
              .find({ tagName : { $in : ['funny','politics'] } })
              .lean()
              .distinct('_id')
              .exec((err, itemTagIds) => {
              if (err) { console.error(err); }
              Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
              console.log(items); // Items filtered by tagName
              });
              });





              share|improve this answer


























                0












                0








                0







                After having the same problem myself recently, I've come up with the following solution:



                First, find all ItemTags where tagName is either 'funny' or 'politics' and return an array of ItemTag _ids.



                Then, find Items which contain all ItemTag _ids in the tags array



                ItemTag
                .find({ tagName : { $in : ['funny','politics'] } })
                .lean()
                .distinct('_id')
                .exec((err, itemTagIds) => {
                if (err) { console.error(err); }
                Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
                console.log(items); // Items filtered by tagName
                });
                });





                share|improve this answer













                After having the same problem myself recently, I've come up with the following solution:



                First, find all ItemTags where tagName is either 'funny' or 'politics' and return an array of ItemTag _ids.



                Then, find Items which contain all ItemTag _ids in the tags array



                ItemTag
                .find({ tagName : { $in : ['funny','politics'] } })
                .lean()
                .distinct('_id')
                .exec((err, itemTagIds) => {
                if (err) { console.error(err); }
                Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
                console.log(items); // Items filtered by tagName
                });
                });






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Dec 7 '16 at 20:43









                OllyBarcaOllyBarca

                1,08811228




                1,08811228






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f11303294%2fquerying-after-populate-in-mongoose%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Create new schema in PostgreSQL using DBeaver

                    Deepest pit of an array with Javascript: test on Codility

                    Costa Masnaga