如何在多个 MongoDBs 子文档之一中查找元素

How to find an element in one of several MongoDBs subdocuments

我有一个 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)。 我可以看到我最初可以对 'whole' 子文档执行搜索,以获取匹配文档的数据。但是,我如何编写查询来查找我可以在哪些橱柜中找到物品的详细信息? 另外,有没有更直接的方法找到橱柜?即,如果我知道我想找到香肠,但不知道在哪个橱柜中可以找到它?

我认为这里真正的问题是当前的数据结构不能很好地支持您正在尝试做的事情。有更好的方法可以做到这一点,最重要的是减少任何初始查询的负载,以便在给定的橱柜中找到 "might" 包含所需项目的文档。

考虑 "searching" 的基本前提可能包含文档中 "cupboards" 中的一个 "Sausage" 的文档。您的观察肯定是正确的,在这种结构中,最好搜索 "whole" 来测试是否存在。但是请考虑执行此操作的查询:

collection.find({ "Store.whole.Sausage": { "$exists": True } })

这不是很好。它不理想的原因是因为您正在测试文档中是否存在 "key",这意味着不能使用 "index" 并且整个集合需要 "scanned"为了获得基本水平的结果。

即使获得,然后确定 "which" 个橱柜包含此项目也是一个代码问题,用于迭代对象属性并找到匹配项。在单个文档上这样做通常比延迟到服务器更有意义,但为了一般性说明,当然有使用 mapReduce 的操作,它可以在服务器上 运行 代码和 return 结果与提供的文档不同(作为 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 }
    }
)

哪个 return 像这样:

{
    "results" : [
        {
            "_id" : ObjectId("5563db1c22cfcc577e5d7450"),
            "value" : {
                "cupboards" : [
                    {
                        "cupboard" : 2,
                        "item" : "Sausage",
                        "qty" : 3
                    }
                ],
                "totalQty" : 3
            }
        }
    ]
}

基本上可以在客户端代码中采用相同的方法,您可以在客户端代码中检查文档的内容以找到匹配项。但正如我所说,这里真正的问题是初始 "query" 不是最优的并且 "brute force" 检查集合。

更好的情况是像这样构建数据:

{
    "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" 是一个 "data point",可以对其进行索引以便仅获取与所需项目匹配的文档,而无需扫描整个集合:

collection.find({ "cupboards.item": "Sausage" })

你仍然可以 "filter" 代码中的数组内容来找到你的匹配项,或者使用 .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
}

所以这里的重点是 "avoid" 在存储的文档中使用实际上 "data points" 作为 "key names" 的东西。键名未编入索引,无法进行有效搜索。 "Data" 可以索引,搜索效率高


修改后的结构说明供参考。除了一般的 "overhaul" 之外,这里的一大区别是省略了文档中最初呈现的 "total" 字段。遗漏的一个重要原因是即使在原始形式中,在添加和更新其他密钥的同时维护这样的 "totals" 也是一个可怕的前提。

如果没有 loading/inspecting/re-writing "whole" 文档,基本上无法自动更新所有值并保持 "totals" 同步。单一 "fast" 更新不可能以任何形式出现。

虽然在文档和组件中维护 "totals" 通常是 "noble idea",但对于多个 "total" 来说,开销是相当大的。因此,在大多数情况下,"speedy writes" 通常优于读取所需的额外计算开销。因此,通常最好遵循该模型,除非您发现在您的特定情况下,您可以忍受处理多个更新的额外成本以获得更好的读取操作性能。