0

我正在学习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 文档中嵌入了类别的子文档数组。使根据类别名称分别检索域和 ip 变得容易,但难以一次性检索与特定类别关联的所有域和 ip。下面的代码概述了为什么我相信这一点:

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 未能找到标记方案的任何教程/最佳实践。也可能是我把事情复杂化了。

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

4

1 回答 1

2

AFAIK mongo 查询必须针对一个集合运行。这不是 mongoose 的事实,而是 mongodb 本身的事实。鉴于这一事实,您可以尝试一些可能的设计。每个都有不同的权衡,因此您需要了解对您的应用程序很重要的查询并相应地选择

1)将 IP 和域存储在一个集合中,但每个文档都有一个type属性和相应的属性。

Mongoose 并没有真正设置为促进这种使用模式。如果您的大部分收藏都包含同质文档,Mongoose 效果最好。mongodb 本身也是如此,但并非如此。不推荐,但如果您的使用模式真的需要这个,这不是不可能的。

2)对多个集合并行运行相同的查询。我有一些代码可以在下面执行此操作。这是一个相当讨厌的 hack 内部结构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具有模式的集合,该集合具有一个命名属性,每个集合是一个带有猫鼬的 ObjectId,ref并用于.populate()加载“加入”记录。这几乎是关系数据库中连接表的直接模拟。

{
    category: {type: ObjectId, ref: 'Category'},
    ip: {type: ObjectId, ref 'IP'},
    domain: {type: ObjectId, ref 'Domain'},
}

对于其中Categorized只有 2 个属性中的每条记录实际上将是非空的,您将对.populate('ip').populate('domain')每个查询执行一次。对于每个匹配的文档,集合将有 1 个查询Categorized和 1 个索引查询。_id如果它只是一个关键字标签,您也可以直接存储类别的名称,然后您不需要首先按名称查找类别的 ObjectId。

于 2012-12-10T03:42:13.087 回答