我认为这里真正的问题是当前的数据结构不支持您正在尝试做的事情。有更好的方法可以做到这一点,最重要的是减少任何初始查询的负载,以便在给定的橱柜中找到“可能”包含所需项目的文档。
考虑“搜索”文档的基本前提,该文档可能在文档的“橱柜”之一中包含“香肠”。您的观察肯定是正确的,在这种结构中,最好搜索“整体”以测试是否存在。但请考虑执行此操作的查询:
collection.find({ "Store.whole.Sausage": { "$exists": True } })
这不是很好。它不理想的原因是因为您正在测试文档中是否存在“键”,这意味着不能使用“索引”并且需要“扫描”整个集合才能获得基本的结果水平。
即使一旦获得,确定“哪个”橱柜包含该项目也是一个代码问题,用于迭代对象属性并找到匹配项。在单个文档上执行此操作而不是推迟到服务器通常是有意义的,但一般来说,使用 mapReduce 的操作当然可以在服务器上运行代码并返回与呈现的文档不同的结果(作为外壳示例):
db.collection.mapReduce(
function () {
var Store = this.Store,
id = this._id
Object.keys(Store)
.filter(function(key) {
return key != "whole";
})
.forEach(function(key) {
Object.keys( Store[key] )
.forEach(function(el) {
if ( el == "Sausage" )
emit(id, {
cupboards: [
{
cupboard: parseInt(key.match(/\d+$/)[0]),
item: el,
qty: Store[key][el]
}
],
totalQty: Store[key][el]
});
});
});
},
function (key,values) {
var result = { cupboards: [], totalQty: 0 };
values.forEach(function(el) {
el.cupboards.forEach(function(item) {
result.cupbards.push(item);
});
result.totalQty += el.totalQty;
});
return result;
},
{
"query": { "Store.whole.Sausage": { "$exists": true } },
"out": { "inline": 1 }
}
)
这会返回如下内容:
{
"results" : [
{
"_id" : ObjectId("5563db1c22cfcc577e5d7450"),
"value" : {
"cupboards" : [
{
"cupboard" : 2,
"item" : "Sausage",
"qty" : 3
}
],
"totalQty" : 3
}
}
]
}
在客户端代码中基本上可以遵循相同的方法,您可以在其中检查文档的内容以查找匹配项。但正如我所说,这里真正的问题是初始“查询”不是最优的,是对集合的“蛮力”检查。
更好的情况是像这样构造数据:
{
"cupboards": [
{ "cupboard": 1, "item": "Cheese", "qty": 21 },
{ "cupboard": 1, "item": "Humous", "qty": 25 },
{ "cupboard": 1, "item": "Nachos", "qty": 10 },
{ "cupboard": 1, "item": "Olives", "qty": 10 },
{ "cupboard": 2, "item": "Cheese", "qty": 11 },
{ "cupboard": 2, "item": "Humous", "qty": 9 },
{ "cupboard": 2, "item": "Olives", "qty": 2 },
{ "cupboard": 2, "item": "Sausage", "qty": 3 }
]
}
现在“项目”是一个“数据点”,可以对其进行索引,以便在不扫描整个集合的情况下仅获取与所需项目匹配的那些文档:
collection.find({ "cupboards.item": "Sausage" })
您仍然可以在代码中“过滤”数组内容以找到您的匹配项,或者使用.aggregate() 执行类似的操作:
collection.aggregate([
{ "$match": { "cupboards.item": "Sausage" }},
{ "$unwind": "$cupboards" },
{ "$match": { "cupboards.item": "Sausage" }},
{ "$group": {
"_id": "$_id",
"cupboards": {
"$push": {
"cupboard":"$cupboards.cupboard",
"item": "$cupboards.item",
"qty": "$cupboards.qty"
}
},
"totalQty": { "$sum": "$cupboards.qty" }
}}
])
产生与上述相同的基本结果,但更简单,速度更快:
{
"_id" : ObjectId("5563e80065536add0d04619c"),
"cupboards" : [
{
"cupboard" : 2,
"item" : "Sausage",
"qty" : 3
}
],
"totalQty" : 3
}
所以这里的真正意义是“避免”在存储的文档中使用实际上是“数据点”的东西作为“键名”。键名未编入索引,因此无法进行有效搜索。 “数据”可以被索引,这是搜索的有效方法。
关于修订结构的注释以供参考。除了这里的一般“大修”之外,一个很大的区别是省略了文档中最初显示的“总计”字段。遗漏的一个重要原因是,即使在原始形式中,在添加和更新其他密钥的同时维护这样的“总数”也是一个可怕的前提。
基本上没有办法在不加载/检查/重写“整个”文档的情况下自动更新所有值并保持“总计”同步。任何形式的单一“快速”更新都是不可能的。
虽然在文档和组件中维护“总计”通常是一个“高尚的想法”,但对于多个“总计”而言,开销是相当可观的。因此,在大多数情况下,“快速写入”通常比读取所需的额外计算开销更受欢迎。因此,通常最好遵循该模型,除非您发现在您的特定情况下,您可以忍受处理多个更新以获得更好的读取操作性能的额外成本。