我有一个mongodb集合,其中包含包含以下格式的子文档的文档:
'Store': { 'cupboard1': { 'Cheese': 21,
'Humous': 25,
'Natchos': 10,
'Olives': 10,
'stockItems': 66},
'cupboard2': { 'Cheese': 11,
'Humous': 9,
'Olives': 2,
'Sausage': 3,
'stockItems': 25},
'whole': { 'Chris': 32,
'Olives': 11,
'Sausage': 3,
'Humous': 34,
'Natchos': 10,
'stockItems': 91}
我想构建一些依赖于根据食物名称查找文档的查询(使用python3 / Pymongo)。 我可以看到我最初可以对整个'进行搜索。子文档,以获取匹配的文档的数据。但是,我如何编写查询以查找可以找到项目的橱柜的详细信息? 还有,有更直接的方法找到橱柜?也就是说,如果我知道我想要找到香肠,但不知道哪个橱柜可能会被找到?
答案 0 :(得分:1)
我认为这里真正的问题是数据的当前结构不支持你想要做的很好。有更好的方法可以做到这一点,最重要的是减少任何初始查询的负载,以便找到“可能”包含给定橱柜中所需项目的文档。
考虑在文档中的“橱柜”之一“搜索”可能包含“香肠”的文档的基本前提。你在观察中肯定是正确的,在这种结构中,最好搜索“整体”以测试存在。但请考虑执行此操作的查询:
collection.find({ "Store.whole.Sausage": { "$exists": True } })
那不是很好。它不理想的原因是因为您正在测试文档中是否存在“键”,这意味着无法使用“索引”并且需要“扫描”整个集合才能获得该基础结果水平。
即使一旦获得,然后确定“哪个”橱柜包含此项目是代码问题,以迭代对象属性并找到匹配。在单个文档中,通常有意义而不是推迟到服务器,但是为了一般说明,当然使用mapReduce的操作可以在服务器上运行代码并返回与呈现的文档不同的结果(作为shell示例):
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 }
]
}
现在“item”是一个“数据点”,可以对其进行索引,以便在不扫描整个集合的情况下获取与所需项目匹配的文档:
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
}
所以这里的真正要点是“避免”在存储的文档中使用实际上为“数据点”作为“关键名称”的东西。键名未编入索引,无法进行有效搜索。 “数据”可以编入索引,这是搜索的有效方法。
关于修订结构的说明供参考。除了一般的“大修”之外,一个很大的区别是遗漏了最初提出的文件中存在的“总”字段。遗漏的一个重要原因是即使在原始形式中,在添加和更新其他密钥时维护这样的“总计”也是一个可怕的前提。
基本上没有办法原子地更新所有值并保持“总计”同步而不加载/检查/重写“整个”文档。单数“快速”更新不是任何形式的可能性。
虽然在文档和组件中保持“总数”通常是“高尚的想法”,但是对于超过单个“总计”而言,开销是相当大的。因此,在大多数情况下,“快速写入”通常优于读取所需的额外计算开销。因此,通常最好遵循该模型,除非您发现在特定情况下您可以承担处理多个更新的额外成本以获得更好的读取操作性能。