【问题标题】:How to deal with Many-to-Many relations in MongoDB when Embedding is not the answer?当嵌入不是答案时,如何处理 MongoDB 中的多对多关系?
【发布时间】:2011-10-22 20:10:54
【问题描述】:

这是交易。假设我们在 MongoDB 中有以下数据模式:

  • items:一个包含一些数据的大型文档的集合(这与它实际上是什么完全无关)。
  • item_groups:一个集合,其中包含一个名为 items._id 列表的文档,称为 item_groups.items 以及一些额外的数据。

因此,这两者通过多对多关系联系在一起。但是有一件棘手的事情:出于某种原因,我不能将项目存储在项目组中,所以——正如标题所说——嵌入不是答案。

我真正担心的查询旨在查找包含某些特定项目的特定组(即,我为每个集合设置了一组标准)。事实上,它还必须说明每个找到的组中有多少项符合标准(没有项意味着未找到组)。

到目前为止,我想出的唯一可行的解​​决方案是使用带有虚拟 reduce 函数的 Map/Reduce 方法:

function map () {
    // imagine that item_criteria came from the scope.
    // it's a mongodb query object.
    item_criteria._id = {$in: this.items};
    var group_size = db.items.count(item_criteria);
    // this group holds no relevant items, skip it
    if (group_size == 0) return;

    var key = this._id.str;
    var value = {size: group_size, ...};

    emit(key, value);
}

function reduce (key, values) {
    // since the map function emits each group just once,
    // values will always be a list with length=1
    return values[0];
}

db.runCommand({
    mapreduce: item_groups,
    map: map,
    reduce: reduce,
    query: item_groups_criteria,
    scope: {item_criteria: item_criteria},
});

问题线是:

item_criteria._id = {$in: this.items};

如果 this.items.length == 5000 甚至更多怎么办?我的 RDBMS 后台大声呼喊:

SELECT ... FROM ... WHERE whatever_id IN (over 9000 comma-separated IDs)

绝对不是个好办法

非常感谢你们的宝贵时间,伙计们!

我希望最好的答案是“你太傻了,别再用 RDBMS 风格思考了,使用最新版本 MongoDB 中的 $its_a_kind_of_magicSphere”:)

【问题讨论】:

  • 当谈论“嵌入”时,您是在谈论 DBReferences --> mongodb.org/display/DOCS/…?
  • 我说的是实际嵌入,即将一个文档存储在另一个文档中。例如comment = {user: 'DrColossos', text: '你在说什么?'};问题 = {x: 13, y: 42, cmets: [comment]}.
  • 如果您使用中间表创建多对多关系并进行查询?这不能解决你的问题吗?

标签: mongodb


【解决方案1】:

为什么不使用相反的设计?

您正在存储项目和 item_groups。如果您的第一个想法是将项目存储在 item_group 条目中,那么相反的想法可能不是一个坏主意:-)

让我解释一下:

在每个项目中存储它所属的组。 (你在NOSql中,数据重复没问题!) 例如,假设您在项目条目中存储一个名为组的列表,您的项目如下所示: { _ID : .... , 姓名 : .... , 组: [ObjectId(...), ObjectId(...),ObjectId(...)] }

那么map reduce的思路就很费劲了:

map = function()  {
    this.groups.forEach( function(groupKey) {
        emit(groupKey, new Array(this))
    }
}


reduce = function(key,values) {
   return Array.concat(values);
}


db.runCommand({
   mapreduce : items,
   map : map,
   reduce : reduce,
   query : {_id :  {$in : [...,....,.....] }}//put here you item ids
})

您可以添加一些参数(例如 finalize 以修改 map reduce 的输出),但这可能会对您有所帮助。

当然,如果您需要另一个集合来存储 item_groups 的详细信息,但在某些情况下(如果有关 item_groups 的信息不存在,或者不更改,或者您不在乎)你没有它的最新版本)你根本不需要它们!

这是否给了您解决问题的提示?

【讨论】:

    【解决方案2】:

    我认为您正在努力将域/对象建模与数据库模式建模分开。我在尝试 MongoDb 时也遇到了这个问题。

    为了语义和清晰起见,我将Groups替换为Categories这个词

    本质上,您的理论模型是“多对多”关系,因为每个Item 可以属于Categories,然后每个Category 可以拥有许多Items

    这最好在您的域对象建模中处理,而不是在 DB 模式中处理,尤其是在实现文档数据库 (NoSQL) 时。在您的 MongoDb 模式中,您通过使用顶级文档模型和嵌入的组合来“伪造”“多对多”关系。

    嵌入对于来自 SQL 持久性后端的人来说很难接受,但它是答案的重要组成部分。诀窍在于决定它是浅还是深,单向还是双向等。


    顶级文档模型

    因为您的Category 文档包含它们自己的一些数据,并且被大量Items 大量引用,我同意您的观点,将它们完全嵌入每个Item 是不明智的。

    相反,将ItemCategory 对象都视为顶级文档。确保您的 MongoDb 架构为每个表分配一个表,以便每个文档都有自己的 ObjectId

    下一步是决定嵌入的位置和数量...没有正确答案,因为这完全取决于您如何使用它以及您的扩展目标是什么...

    嵌入决策

    1.项目

    至少,您的Item 对象应具有其类别的集合属性。至少这个集合应该包含每个CategoryObjectId

    我的建议是将您在与Item 交互时最常使用的数据添加到此集合中...

    例如,如果我想在我的网页上以网格的形式列出一堆项目,并显示它们所属类别的名称。很明显,我不需要了解有关 Category 的所有信息,但如果我只嵌入了 ObjectId,则需要进行第二次查询才能获得有关它的任何详细信息。

    最有意义的做法是将类别的Name 属性与ObjectId 一起嵌入到集合中,这样拉回Item 现在可以在不进行其他查询的情况下显示其类别名称。

    要记住的最重要的事情是,嵌入在您的“代表”CategoryItem 中的键/值对象不必匹配真正的 Category 文档模型......它不是 OOP 或关系数据库建模。

    2.类别

    相反,您可以选择单向嵌入,并且在您的Category 文档中没有任何Item 信息...或者您可以选择为项目数据添加一个集合,就像上面一样(ObjectId , 或ObjectId + Name)...

    在这个方向上,我个人倾向于不嵌入任何东西......如果我想要我的类别的 Item 信息,我想要很多,而不仅仅是一个名字......和深度嵌入顶级文档(项目)没有意义。我只会让自己在数据库中查询一个 Items 集合,其中每个集合在它的 Categories 集合中都拥有我的 Category 的 ObjectId。

    唷...肯定会令人困惑。关键是,您有一些数据重复,并且您必须根据您的使用情况调整您的模型以获得最佳性能。好消息是,这正是 MongoDb 和其他文档数据库所擅长的......

    【讨论】:

      猜你喜欢
      • 2020-10-09
      • 2018-09-17
      • 1970-01-01
      • 1970-01-01
      • 2021-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多