2011-08-09 20 views
6

这是交易。让我们假设我们有以下数据模式在MongoDB中:当嵌入不是答案时如何处理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}; 

如果这.items.length == 5000甚至更多?我的RDBMS后台哭出声来:

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

是绝对不会去的好方法。

谢谢你为你的时间留下很多,伙计!

我希望最好的答案是这样的:“你很愚蠢,停止思考在RDBMS风格,使用$ its_a_kind_of_magicSphere从MongoDB的最新发布的” :)

+0

在谈到“嵌入”你在说什么DBReferences - > http://www.mongodb.org/display/DOCS/数据库+参考#DatabaseReferences-的Javascript%28mongoshell%29? – DrColossos

+0

我正在谈论实际的嵌入,即将一个文档存储在另一个文档中。例如评论= {用户:'DrColossos',文本:'你在说什么?'};问题= {x:13,y:42,评论:[评论]}。 –

+0

如果使用中间表创建多对多关系并查询它?不会解决你的问题? – Qqbt

回答

1

为什么不使用反设计?

您正在存储项目和item_groups。如果你的第一个想法来存储item_group项物品,那么也许相反的是不是一个坏主意:-)

让我解释一下:在你存储它所属的组的每个项目

。 (你在NOSql中,数据重复是可以的!) 例如,假设你在项目条目中存储一个名为groups的列表,并且你的项目如下所示: {_id:.... ,name:.... ,组:[的ObjectId(...)的ObjectId(...)的ObjectId(...)]}

然后映射减少的想法需要很多力量:

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 
}) 

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

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

这是否为您提供有关解决问题的提示?

4

我认为你正在努力从数据库模式建模中分离域/对象建模。在尝试使用MongoDb时,我也遇到了这个问题。

对于语义和清楚起见,我将代替GroupsCategories

本质的理论模型,每个Item一个“多对多”的关系可以属于Categories,每个Category可以拥有许多Items

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

嵌入是难以下咽的人从SQL持久后端来了,但它答案的重要组成部分。诀窍是决定它是否是浅或深,单向或双向等


顶级文档模型

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

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

下一步是决定在哪里,有多少嵌入...没有正确答案,因为这一切都取决于你如何使用它,你的缩放的野心是什么?

嵌入决定

1.用品

至少,你Item对象应为其类别集合属性。至少这个集合应该包含ObjectId每个Category

我的建议是添加到该集合,你用Item最常进行交互时所使用的数据...

例如,如果我想列出一堆物品在我的网页上网格,并显示他们所属类别的名称。很显然,我并不需要了解的一切Category,但如果我只有嵌入的ObjectId,第二个查询会得到任何细节有关它在所有必要的。

相反,最有意义的是将类别的Name属性与ObjectId一起嵌入到集合中,以便现在可以在不用另一个查询的情况下显示其类别名称。

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

2.分类

反向你可以选择离开嵌入单向的,而不是有任何Item信息在你的Category文件...或者你可以选择添加对项目数据收集多像上面(ObjectId,或ObjectId + Name)...

在这个方向,我会亲自向精益无事可做嵌入式......更可能的,如果我想为我的类别Item信息,我想它的很多,不仅仅是一个名字......并深入嵌入顶级文档nt(Item)没有意义。我只是简单地致辞去查询数据库中的Items集合,其中每个集合都拥有我的Category的ObjectId在它的Categories集合中。

呼...混乱是肯定的。问题的关键是,你有一些数据重复和你有你的模型调整您的使用以获得最佳性能。好消息是,那是什么的MongoDB和其他文件数据库擅长...