mongodb是否提供查询子文档集合以查找父文档?

时间:2012-12-10 00:09:56

标签: node.js mongodb mongoose

我正在学习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在查找标记方案的任何教程/最佳实践方面都没有让我失望。也可能是因为我过于复杂化了。

根据共享子文档检索不同父文档的最佳方法是什么?

1 个答案:

答案 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。