这是交易。假设我们在MongoDB中有以下数据模式:
items
:包含大量文档的集合,其中包含一些数据(与实际情况完全无关)。item_groups
:包含文件的集合,其中包含名为items._id
的{{1}}列表以及一些额外数据。所以,这两者是以多对多的关系捆绑在一起的。但是有一个棘手的问题:由于某种原因,我不能在项目组中存储项目,所以 - 正如标题所说 - 嵌入不是答案。
我真正担心的查询是为了找到一些包含某些特定项目的特定组(即我为每个集合设置了一组标准)。事实上,它还必须说明每个找到的组中的项目符合标准(没有项目意味着没有找到组)。
我提出的唯一可行解决方案是使用具有虚拟缩减功能的Map / Reduce方法:
item_groups.items
问题在于:
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},
});
如果this.items.length == 5000甚至更多,该怎么办?我的RDBMS背景大声呼喊:
item_criteria._id = {$in: this.items};
绝对不是一个好方法。
非常感谢你的时间,伙计们!
我希望最好的答案是“你是愚蠢的,不再考虑RDBMS风格,使用最新版本的MongoDB中的 $ its_a_kind_of_magicSphere ”:)
答案 0 :(得分:4)
我认为您正在努力将域/对象建模与数据库架构建模分离。在尝试使用MongoDb时,我也很挣扎。
为了语义和清晰度,我将用Groups
替换Categories
基本上,您的理论模型是“多对多”关系,因为每个Item
可以属于Categories
,每个Category
可以拥有许多Items
。
最好在域对象建模中处理,而不是在DB模式中处理,尤其是在实现文档数据库(NoSQL)时。在您的MongoDb架构中,您通过使用顶级文档模型和嵌入的组合来“伪造”“多对多”关系。
嵌入对于来自SQL持久性后端的人来说很难接受,但 是 是答案的重要部分。诀窍在于决定它是浅层还是深层,单向或双向等等。
顶级文档模型
由于您的Category
文档包含自己的一些数据并且被大量Items
大量引用,我同意您的看法,将它们完全嵌入到每个Item
中是不明智的。
相反,将Item
和Category
对象视为顶级文档。确保您的MongoDb架构为每个架构分配一个表,以便每个文档都有自己的ObjectId
。
下一步是决定嵌入的位置和数量......没有正确的答案,因为这一切都取决于你如何使用它以及你的扩展目标......
嵌入决策
1。项目
至少,您的Item
个对象应该具有其类别的集合属性。至少此集合应包含ObjectId
的{{1}}。
我的建议是在此集合中添加您最常与Category
交互时使用的数据......
例如,如果我想在网格中列出我的网页上的一堆项目,并显示它们所属的类别的名称。很明显,我不需要了解Item
的所有内容,但如果我只嵌入了ObjectId,则需要第二个查询来获取有关它的任何细节。
相反,最有意义的是将类别的Category
属性与Name
一起嵌入集合中,以便拉回ObjectId
现在可以显示其类别名称而无需另一个查询。
要记住的最重要的事情是,Item
中嵌入的“代表”Item
的键/值对象不必与真实的Category
文档模型匹配...它不是OOP或关系数据库建模。
2。分类
相反,您可以选择单向嵌入,并且Category
文档中没有任何Item
信息...或者您可以选择为项目数据添加一个集合,就像上面一样(Category
或ObjectId
+ ObjectId
)...
在这个方向上,我个人倾向于没有嵌入任何内容......如果我想要我的类别的Name
信息,我想要很多,不仅仅是一个名字......而且深刻 - 嵌入顶级文档(Item)毫无意义。我只想让自己在数据库中查询一个Items集合,其中每个集合都在其类别集合中拥有了我的Category的ObjectId。
答案 1 :(得分:1)
为什么不使用相反的设计?
您正在存储项目和item_groups。如果你的第一个想法是将项目存储在item_group条目中,那么可能反之亦然: - )
让我解释一下:
在每个项目中存储它所属的组。 (你在NOSql中,数据重复没问题!) 例如,假设您在项目条目中存储了一个名为groups的列表,您的项目如下所示: { _ID : .... , 名称 : .... ,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
})
您可以添加一些参数(例如,最终确定以修改地图缩小的输出),但这可能会对您有所帮助。
当然,你需要有另一个集合来存储item_groups的详细信息,如果你需要它,但在某些情况下(如果这个关于item_groups的信息不存在,或者没有改变,或者你不关心你没有最新版本的版本)你根本不需要它们!
这能否为您提供有关问题解决方案的提示?