查询后填入猫鼬

我对Mongoose和MongoDB一般很新,所以我很难搞清楚这样的事情是否可能:

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 }); 

有没有更好的办法做到这一点?

编辑

对任何混淆道歉。 我想要做的是获得所有包含有趣的标签或政治标签的项目。

编辑

没有where子句的文档:

 [{ _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 }, ... ] 

用where子句,我得到一个空数组。

你所要求的不是直接支持,而是可以通过在查询返回后添加另一个过滤步骤来实现。

首先, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )绝对是您需要做的过滤标签文档。 那么在查询返回之后,您将需要手动过滤出没有符合填充条件的任何tags文档的文档。 就像是:

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

尝试更换

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

通过

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

首先:我知道这个问题确实已经过时了,但是我找到了这个问题,所以这个帖子就是Google条目#1。 所以我实现了docs.filter版本(接受的答案),但是当我在mongoose v4.6.0文档中阅读时,我们现在可以简单地使用:

 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' }) 

希望这有助于未来的搜索机器用户。

使用大于3.2的现代MongoDB,在大多数情况下,可以使用$lookup作为.populate()的替代。 这也具有实际上在服务器上执行连接的优点,而不是什么.populate()实际上是“多个查询”来“模拟”一个连接。

所以.populate()实际上不是一个“连接”,就像关系数据库那样。 另一方面, $lookup操作符实际上是在服务器上工作的,与“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" } ) 

NB这里的.collection.name实际上是赋值给“string”,它是分配给模型的MongoDB集合的实际名称。 由于默认情况下,mongoose“复位”集合名称, $lookup需要实际的MongoDB集合名称作为参数(因为它是一个服务器操作),所以这是一个方便的技巧用于猫鼬代码,而不是“硬编码”集合直接命名。

虽然我们也可以在数组上使用$filter来移除不需要的项目,但实际上这是最有效的形式,这是由于针对as $lookup的特殊条件的聚合管道优化 ,其次是$unwind$match条件。

这实际上导致三个流水线阶段被分成一个阶段:

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

这是非常优化的,因为实际操作“过滤集合首先加入”,然后返回结果并“展开”数组。 这两种方法都被采用,所以结果不会破坏16MB的BSON限制,这是客户端不具备的限制。

唯一的问题是,它在某些方面似乎是“反直觉的”,特别是当你想要一个数组的结果时,这就是$group在这里的原因,因为它重建为原始文档形式。

同样不幸的是,我们现在根本不能在服务器使用的相同语法中真正写$lookup 。 恕我直言,这是一个疏忽要纠正。 但是现在,简单地使用序列就可以工作,并且是最好的选择,具有最好的性能和可扩展性。


工作示例

下面给出一个在模型上使用静态方法的例子。 一旦这个静态方法被实现,调用就会变成:

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

在结构上与.populate()非常相似,但实际上是在服务器上进行连接。 为了完整性,这里的用法根据父例和子例将返回的数据返回到猫鼬的文档实例。

这是相当微不足道的,很容易适应或只是使用最常见的情况下。

NB在这里使用async只是为了简化运行附带的例子。 实际的实现没有这种依赖性。

 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(); } ) 

最近自己也有同样的问题,我想出了以下解决方案:

首先,找到tagName为“funny”或“politics”的所有ItemTag,并返回一个ItemTag _ids数组。

然后,找到标签数组中包含所有ItemTag _ids的项目

 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 }); }); 

@aaronheckmann的答案为我工作,但我不得不取代return doc.tags.length; return doc.tags != null; 因为如果该字段与填充内写入的条件不匹配,则该字段包含null 。 所以最终的代码是:

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