mongodb聚合框架由嵌套文档匹配

时间:2014-02-07 09:33:24

标签: mongodb mongodb-query aggregation-framework

我有以下文件清单:

{
    "_id" : "Tvq579754r",
    "name": "Tom",
    "forms": {
           "PreOp":{
             "status":"closed"          
           },

           "Alert":{
             "status":"closed"          
           },

           "City":{
              "status":"closed"         
           },

          "Country":{
             "status":"closed"          
          } 
    }
},
....
{
    "_id" : "Tvq444454j",
    "name": "Jim",
    "forms": {
          "Jorney":{
             "status":"closed"          
           },

          "Women":{
             "status":"void"            
          },

         "Child":{
            "status":"closed"           
         },

         "Farm":{
           "status":"closed"            
         }  
     }
}

我想通过'status'字段('forms.name_of_form.status')过滤它们。我需要获取所有没有'forms.name_of_form.status'等于'void'的文档。

预期结果是(没有无效表格状态的文件):

{
    "_id" : "Tvq579754r",
    "name": "Tom",
    "forms": {
           "PreOp":{
             "status":"closed"          
           },

           "Alert":{
             "status":"closed"          
           },

           "City":{
              "status":"closed"         
           },

          "Country":{
             "status":"closed"          
          } 
    }
}

1 个答案:

答案 0 :(得分:24)

如果事先不知道所有可能的forms名称,并在查询中使用它们,则无法查询此结构以获得所需的结果。无论如何,它会非常混乱。也就是说,继续阅读,我将解释如何做到这一点。

这些文档的结构存在问题,这会阻止您进行任何合理的查询分析。就目前而言,您必须知道所有可能的表单名称字段才能过滤掉任何内容。

您当前的结构包含一个包含子文档的表单,其中每个键包含另一个具有单个属性status的子文档。这很难遍历,因为forms元素对于您创建的每个文档都有任意结构。这意味着下降到您想要比较集合中每个文档的status信息的模式。

这就是我所说的路径。要获得任何元素的状态,您必须执行以下操作

  

表格 - > PreOp - >状态

     

表格 - > 提醒 - >状态

随着第二个元素一直在变化。 没有办法 通配符这样的东西,因为命名被认为是显式的。

这可能被认为是实现从表单序列化数据的简单方法,但我看到更灵活替代方案。您需要的是一个可以标准模式遍历的文档结构。这在设计中总是值得考虑的事情。请采取以下措施:

{
    "_id" : "Tvq444454j",
    "name": "Jim",
    "forms": [
        {
             "name": "Jorney",
             "status":"closed"          
        },
        {
            "name": "Women",
            "status":"void"            
        },
        {
            "name": "Child",
            "status":"closed"           
        },
        {
            "name": "Farm",
            "status":"closed"            
        }  
    ]
}

因此,更改文档的结构以使forms元素成为一个数组,而不是将状态字段放在一个名为“表单字段”的键下,我们将数组的每个成员作为一个子-document包含“表单字段”namestatus。因此,标识符和状态仍然配对在一起,但现在只是表示为子文档。这最重要的是改变了这些键的访问路径,就像现在两者字段名称和我们可以做的状态一样

  

表格 - >的状态

     

     

表格 - >的名称

意味着您可以查询formstatusform字段中所有字段的名称,甚至是具有特定name字段和某些status的所有文档。这比原始结构更好

现在,在您的特定情况下,您希望 所有字段不是void的文档。现在单个查询中没有办法做到这一点,因为没有运算符以这种方式比较数组中的所有元素并查看它们是否相同。但是你可以采取两种方法:

第一个也可能效率不高的是查询包含formsstatus为“{”的元素的所有文档。使用生成的文档ID,您可以发出另一个查询,该查询返回具有指定ID的文档。

db.forms.find({ "forms.status": "void" },{ _id: 1})

db.forms.find({ _id: $not: { $in: [<Object1>,<Object2>,<Object3>,... ] } })

鉴于结果大小,这可能是不可能的,并且通常不是一个好主意,因为排除运算符$not基本上强制集合的完整扫描,因此您无法使用索引。

另一种方法是使用聚合管道,如下所示:

db.forms.aggregate([
    { "$unwind": "$forms" },
    { "$group": { "_id": "$_id", "status": { "$addToSet": "$forms.status" }}},
    { "$unwind": "$status" },
    { "$sort": { "_id": 1, "status": -1 }},
    { "$group": { "_id": "$_id", "status": { "$first": "$status"}}},
    { "$match":{ "status": "closed" }}
])

当然,只会返回匹配的文档的_id,但您可以使用$ in发出查询并返回整个匹配的文档。这比之前使用的排除运算符更好,现在我们可以使用索引来避免完整的集合扫描。

作为最终方法和最佳性能考虑因素,您可以再次更改文档,以便在顶层保持“状态”表单中的任何字段是否为“void”或“关闭”。因此,在顶层,只有当所有项目都“无效”时才会关闭该值,如果某些内容无效,则该值将被“无效”,等等。

最后一个意味着进一步的程序化更改,forms项的所有更改都需要更新此字段以维持“状态”。然而,这是找到所需文件的最有效方式,值得考虑。


修改

除了将文档更改为具有主状态之外,修改后的结构上最快的查询形式实际上是:

db.forms.find({ "forms": { "$not": { "$elemMatch": { "status": "void" } } } })