我正在学习node.js和mongodb。在许多教程的推荐下,我正在使用mongoose来帮助与mongo进行交互。为了使事情变得复杂,我有一个重要的RDMS背景,并且正在尽我所能来打击我的想法,通过SQL镜头看到mongodb。
现在我正在努力解决查询子文档的问题。我已经找到了如何基于子文档的属性查询父文档,但无法通过直接查询子文档来弄清楚如何查询所有父文档(无论类型)。为了说明我有以下设计的示例模式:
// subdocument
var CategorySchema = new Schema({
name: { type: String, required: true }
});
var IpSchema = new Schema({
ip_address: { type: String, required: true, index: true }
,categories: [CategorySchema]
});
var DomainSchema = new Schema({
domain_name: { type: String, required: true, index: true }
,categories: [CategorySchema]
});
var ip = mongoose.model('Ip', IpSchema);
var domain = mongoose.model('Domain', DomainSchema);
var category = mongoose.model('Category', CategorySchema);
上面的模式在每个存储的域和ip文档中嵌入了一个类别的子文档数组。可以根据类别名称轻松检索域和ips ,但很难一次性检索与特定类别关联的所有域和ips。以下代码概述了为什么我相信这一点:
category.find(function (err, tcs) {
console.log(tcs); // contains an empty set because no categories stored here
});
ip.find({ 'categories.name' : req.params.category }, function(err, ips) {
console.log(ips); // contains all parent documents w/ subdocument name
});
domain.find({ 'categories.name' : req.params.category }, function(err, ips) {
console.log(ips); // contains all parent documents w/ subdocument name
});
现在我可以将上述查询的结果结合起来,但这似乎有点脆弱 - 假设我在越来越多的文档上重用了类别。这会让我存储类别,然后通过类别ID嵌入引用吗?这似乎会在为了优化读取而编写时增加流失。不幸的是,我的Googlefu在查找标记方案的任何教程/最佳实践方面都没有让我失望。也可能是因为我过于复杂化了。
根据共享子文档检索不同父文档的最佳方法是什么?
答案 0 :(得分:2)
AFAIK mongo查询必须针对一个集合运行。这不是一个愚蠢的事实,而是一个mongodb本身的事实。鉴于这一事实,您可以尝试一些可能的设计。每个都有不同的权衡,因此您需要了解对您的应用程序很重要的查询并相应地选择
1)将IP和域存储在一个集合中,但每个文档都具有type
属性和相应的属性。
Mongoose并没有真正设置为促进这种使用模式。如果您的大部分藏品都包含同质文件,那么Mongoose效果最佳。 mongodb本身也是如此,但事实并非如此。如果你的使用模式真的需要这个,不推荐,但不是不可能的。
2)并行对多个集合运行相同的查询。我有一些代码可以在下面执行此操作。对于Mongoose.Query
的内部来说,这是一个相当讨厌的攻击,但它确实有效。
var _ = require('underscore');
var async = require('async');
function multiModelFind(query, models, outerCallback) {
var queries = _.map(models, function (Model) {
var otheModelQuery = new Query();
var state = _.pick(query,
'_conditions',
'_fields',
'_updateArg',
'op',
'options',
'safe'
);
state.model = Model;
_.extend(otheModelQuery, state);
return otheModelQuery;
});
async.map(queries, function (query, callback) {
query.exec(callback);
}, function (error, models) {
outerCallback(error, _.flatten(models));
});
}
样本用法:
var query = IP.find({"categories.name": "foo");
multiModelfind(query, [IP, Domain], function (error, ipsAndDomains) {/*...*/});
我认为这对少数收藏品来说是可行的,但不止一小部分,你可能需要转向选项3。
3)创建了一个Categorized
集合,其架构为每个集合都有一个命名属性,该集合是一个带有猫鼬ref
的ObjectId并使用.populate()
来加载“已加入”记录。这几乎是关系数据库中连接表的直接模拟。
{
category: {type: ObjectId, ref: 'Category'},
ip: {type: ObjectId, ref 'IP'},
domain: {type: ObjectId, ref 'Domain'},
}
对于Categorized
中的每条记录,这些属性中只有2个实际上是非空的,并且您将在每个查询上执行.populate('ip').populate('domain')
。对于每个匹配的文档,Categorized
集合和_id
的1个索引查询将有1个查询。如果它只是一个关键字标签,您也可以直接存储该类别的名称,然后您不需要首先按名称查找该类别的ObjectId。