繁体   English   中英

嵌入时如何处理MongoDB中的多对多关系不是答案?

[英]How to deal with Many-to-Many relations in MongoDB when Embedding is not the answer?

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

  • items :包含大量文档的集合,其中包含一些数据(与实际情况完全无关)。
  • item_groups :包含items._id文件的文档集合,名为item_groups.items加上一些额外数据。

所以,这两者是以多对多的关系联系在一起的。 但是有一个棘手的问题:由于某种原因我不能在项目组中存储项目,所以 - 正如标题所说 - 嵌入不是答案。

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

我提出的唯一可行解决方案是使用具有虚拟缩减功能的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 ”:)

我认为你正在努力将域/对象建模与数据库模式建模分离。 在尝试使用MongoDb时,我也很挣扎。

为了语义和清晰度,我将使用单词Categories替换Groups

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

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

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


顶级文档模型

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

而是将ItemCategory对象视为顶级文档。 确保MongoDb模式为每个模式分配一个表,以便每个文档都有自己的ObjectId

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

嵌入决策

1.项目

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

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

例如,如果我想在网格中列出我的网页上的一堆项目,并显示它们所属的类别的名称。 很明显,我不需要了解有关Category所有内容,但如果我只嵌入了ObjectId,则需要第二个查询来获取有关它的任何详细信息。

相反,最有意义的是将Category的Name属性与ObjectId一起嵌入集合中,以便拉回Item现在可以显示其类别名称而无需另一个查询。

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

2.分类

反过来,您可能选择单向嵌入,而在Category文档中没有任何Item信息...或者您可能选择像上面那样添加项目数据的集合( ObjectIdObjectId + Name )...

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

Phew ......确实令人困惑。 问题的关键是,你有一些数据的重复,你不得不将模型调整您的使用以获得最佳性能。 好消息是,这就是MongoDb和其他文档数据库擅长的......

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

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

让我解释:

在每个项目中,您存储它所属的组。 (您在NOSql中,数据复制正常!)例如,假设您在项目条目中存储了一个名为groups的列表,您的项目如下所示:{_ id:....,name:....,groups:[ 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
})

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

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

这会给你一个关于问题解决方案的暗示吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM