Node.js Mongoose中填充后的查询
一般来说,我对猫鼬和MongoDB都很陌生,所以我很难弄清楚这样的事情是否可行:Node.js Mongoose中填充后的查询,node.js,mongodb,mongoose,Node.js,Mongodb,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' },
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').where('tags.tagName').in(['funny', 'politics'])
借
您请求的内容不受直接支持,但可以通过在查询返回后添加另一个筛选步骤来实现 首先,
.populate('tags',null,{tagName:{$in:['funcy','politics']}})
绝对是筛选标记文档所需的操作。然后,在查询返回后,您需要手动筛选出没有任何符合填充条件的标记的文档。比如:
query....
.exec(function(err, docs){
docs = docs.filter(function(doc){
return doc.tags.length;
})
// do stuff with docs
});
更新:请查看评论-此答案与问题不匹配,但可能它回答了用户遇到的其他问题(我认为这是由于投票结果),因此我不会删除此“答案”:
第一:我知道这个问题确实过时了,但我搜索的正是这个问题,所以这篇文章是谷歌的条目#1。因此,我实现了docs.filter
版本(已接受的答案),但正如我在中所读到的,我们现在可以简单地使用:
Item.find({}).populate({
路径:“标记”,
匹配:{标记名:{$in:['funcy','politics']}
}).exec((错误,项目)=>{
console.log(items.tags)
//仅包含标记名为“有趣”或“政治”的标记
})
希望这对未来的搜索机用户有所帮助。最近我自己也遇到了同样的问题,我提出了以下解决方案:
首先,找到标记名为“有趣”或“政治”的所有ItemTag,并返回ItemTag\u ID数组
然后,在标记数组中查找包含所有ItemTag\u ID的项
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
});
});
对于大于3.2的现代MongoDB,在大多数情况下,您可以将其用作.populate()
的替代品。与.populate()
所做的实际上是“模拟”一个连接的“多个查询”相比,这还具有“在服务器上”实际执行连接的优势
因此,.populate()
不是关系数据库如何实现的真正意义上的“连接”。另一方面,操作员实际上在服务器上执行工作,或多或少类似于“左连接”:
N.B.此处的.collection.name
实际计算结果为“字符串”,即分配给模型的MongoDB集合的实际名称。由于mongoose默认将集合名称“复数化”,并且需要实际的MongoDB集合名称作为参数(因为它是一个服务器操作),因此这是一个在mongoose代码中使用的简便技巧,而不是直接对集合名称进行“硬编码”
虽然我们也可以在阵列上使用来移除不需要的项目,但这实际上是最有效的形式,因为as的特殊条件后跟an和a条件
这实际上导致三个管道阶段合并为一个:
{ "$lookup" : {
"from" : "itemtags",
"as" : "tags",
"localField" : "tags",
"foreignField" : "_id",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"tagName" : {
"$in" : [
"funny",
"politics"
]
}
}
}}
这是非常优化的,因为实际操作“首先过滤要加入的集合”,然后返回结果并“展开”数组。这两种方法都采用,因此结果不会突破16MB的BSON限制,这是客户端没有的限制
唯一的问题是,它在某些方面似乎“违反直觉”,特别是当您希望在数组中显示结果时,但这就是此处的目的,因为它将重建为原始文档表单
同样令人遗憾的是,我们此时根本无法使用服务器使用的最终语法进行编写。嗯,这是一个需要纠正的疏忽。但就目前而言,简单地使用序列将是可行的,并且是具有最佳性能和可伸缩性的最可行的选择
附录-MongoDB 3.6及以上
尽管此处显示的模式由于其他阶段如何进入而得到了相当优化,但它确实存在一个缺陷,即两个阶段通常固有的“左连接”以及populate()
的操作被此处的“最佳”使用否定,而此处不保留空数组。您可以添加preserveNullAndEmptyArrays
选项,但这会否定上面描述的“优化”序列,并基本上保留通常在优化中组合的所有三个阶段
MongoDB 3.6以“更具表现力”的形式进行扩展,允许使用“子管道”表达式。这不仅满足了保留“左连接”的目标,而且还允许使用一个优化的查询来减少返回的结果,并且语法非常简化:
Item.aggregate([
{ "$lookup": {
"from": ItemTags.collection.name,
"let": { "tags": "$tags" },
"pipeline": [
{ "$match": {
"tags": { "$in": [ "politics", "funny" ] },
"$expr": { "$in": [ "$_id", "$$tags" ] }
}}
]
}}
])
用于将声明的“local”值与“foreign”值匹配的方法实际上是MongoDB现在使用原始语法“内部”所做的。通过以这种形式表达,我们可以自己在“子管道”中定制初始表达
事实上,作为一个真正的“聚合管道”,您可以在这个“子管道”表达式中使用聚合管道做任何事情,包括将级别“嵌套”到其他相关集合
进一步的使用有点超出了问题的范围,但即使是“嵌套人口”,新的使用模式也允许这种情况基本相同,并且在充分使用时“更强大”
工作示例
下面给出了在模型上使用静态方法的示例。一旦实现了该静态方法,调用就变成:
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
或者更现代一点,甚至成为:
let results = await Item.lookup({
path: 'tags',
query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
})
使其在结构上非常类似于.populate()
,但实际上它是在服务器上进行连接。为了完整性,使用
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
let results = await Item.lookup({
path: 'tags',
query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
})
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();
}
)
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()
}
})()
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()
}
})()
query....
.exec(function(err, docs){
docs = docs.filter(function(doc){
return doc.tags != null;
})
// do stuff with docs
});