计算与给定条件匹配的子文档

时间:2016-05-27 09:52:32

标签: mongodb mongodb-query aggregation-framework

我的Mongo集合包含以下格式的文档:

{
    ...
    "notifications": [
        {
            "enabled": true,
            "groups": [ "NG1", "NG3" ]
        },
        {
            "enabled": false,
            "groups": []
        }
    ]
}

其中enabled是布尔值,groups是字符串列表。 我需要执行查询以确定notifications中有多少条目enabled = true并且groups中包含给定字符串(例如NG3)。

以前,如果没有后来作为要求引入的enabled属性,我的查询只是

db.collection.find({ "notifications.groups": "NG3" })

我尝试了一些$and运营商的组合,但没有运气,所以欢迎任何建议。提前谢谢!

3 个答案:

答案 0 :(得分:7)

建议运行聚合框架管道,该管道在 {{中使用 $filter $size 数组运算符的组合3}} 管道步骤。

$project 运算符将返回一个数组,该数组包含与指定条件匹配的数组子集中的元素。 $filter 只会返回该已过滤数组中的元素数。

所以,完全放上这个,你就可以运行这个管道,这样你就可以确定通知中有多少条目enabled = true并且在组中包含给定的字符串(例如" NG3"):

var pipeline = [
    { "$match": { "notifications.enabled": true, "notifications.groups": "NG3" } },
    {
        "$project": {
            "numberOfEntries": {
                "$size": {
                    "$filter": {
                        "input": "$notifications",
                        "as": "items",
                        "cond": { 
                            "$and": [
                                { "$eq": [ "$$items.enabled", true ] },
                                { "$setIsSubset": [ [ "NG3" ], "$$items.groups" ] }
                            ] 
                        }
                    }
                }
            }
        }
    }
];

db.collection.aggregate(pipeline);

以上适用于MongoDB版本3.2.X及更新版本。但是,对于涵盖MongoDB版本2.6.X up to and including 3.0.X的解决方案,其他数组运算符(如 $size $map )将是良好的替代运算符 过滤数组。

考虑使用 $setDifference 运算符,使用与$ cond中相同的逻辑过滤数组作为映射表达式。 $map 运算符然后返回一个集合,其中的元素出现在第一个集合中但不出现在第二个集合中;即执行第二组相对于第一组的相对补充。在这种情况下,它将通过enabledgroups属性返回包含与父文档无关的元素的最终通知数组。

var pipeline = [   
    { "$match": { "notifications.enabled": true, "notifications.groups": "NG3" } },
    {
        "$project": {
            "numberOfEntries": {
                "$size": {
                    "$setDifference": [
                        {
                            "$map": {
                                "input": "$notifications",
                                "as": "items",
                                "in": {
                                    "$cond": [
                                        { "$and": [
                                            { "$eq": [ "$$items.enabled", true ] },
                                            { "$setIsSubset": [ [ "NG3" ], "$$items.groups" ] }
                                        ] },
                                        "$$items",
                                        false
                                    ]
                                }
                            }
                        },
                        [false]
                    ]
                }
            }
        }
    }
];

db.collection.aggregate(pipeline);

对于没有上述运营商的旧版MongoDB,请考虑使用 $setDifference $match {{3 }} 运算符实现相同的目标:

var pipeline = [
    { "$match": { "notifications.enabled": true, "notifications.groups": "NG3" } },
    { "$unwind": "$notifications" },
    { "$match": { "notifications.enabled": true, "notifications.groups": "NG3" } },
    {
        "$group": {
            "_id": "$_id",
            "numberOfEntries": { "$sum": 1 }
        }
    }
];
db.collection.aggregate(pipeline);

答案 1 :(得分:4)

计算文件

使用$elemMatch

db.collection.find({
    "notifications": {
         "$elemMatch": {
               "enabled": true,
               "groups": "NG3"
         }
     }
})

$elemMatch运算符匹配包含数组字段的文档,其中至少有一个元素符合所有指定的查询条件。

以下查询的问题:

db.collection.find({ "notifications.groups": "NG3", "notifications.enabled": true })

字段引用是否仅限于单个通知。因此,只要其中一个通知为enabled,此查询就会匹配 作为true,其中一个NG3中包含groups,但您希望将这两个属性应用于同一通知。为了限制匹配过程,您应该使用$elemMatch运算符。

单独计算通知

如果要计算具有该条件的通知数,则应使用聚合管道,正如@chridam在其答案中深入解释的那样。  我建议使用以下四个阶段来计算通知:

  1. 扔掉所有内容并保留通知
  2. 展开notifications数组,生成一个通知 每个数组元素的文档
  3. 使用{enabled: true, groups: "NG3"}

  4. 保留这些通知
  5. 计算剩余的通知

  6. 然后定义与每个阶段相对应的这四个变量:

    var keepNotification = {$project: {
                                          "notifications.enabled": 1, 
                                          "notifications.groups": 1, 
                                          _id: 0
                                      }
                           }
    
    var expandNotifications = {$unwind: "$notifications"}
    
    var filterByEnabledAndGroups = {$match: {
                                                "notifications.enabled": true,  
                                                "notifications.groups": "NG3"
                                            }
                                   }
    
    var count = {$group: {_id: "notifications", count: {$sum: 1}}}
    

    并在汇总管道中使用它们:

    db.collection.aggregate([
                                keepNotification, 
                                expandNotifications, 
                                filterByEnabledAndGroups, 
                                count
                           ])
    

    最终结果如下:

    { "_id" : "notifications", "count" : 5 }
    

答案 2 :(得分:2)

实际上,最好的方法是使用MongoDB 3.2或更高版本,因为您可以使用此$filter中所示的answer运算符,或利用$sum累加器运算符在$project阶段,可用于返回数组中所有元素的总和。

让我们看看如何使用$sum阶段中的$project来完成此操作。

$project阶段,您需要使用$map运算符返回一个数组,其中数组中的项目为“数字”10。要做到这一点,我们需要使用$cond运算符的逻辑条件,当条件为1时返回true,而0时返回false

db.collection.aggregate([
    { "$match": { 
        "notifications.enabled": true, 
        "notifications.groups": "NG3" 
    }},
    { "$project": { 
        "count": { 
            "$sum": { 
                "$map": { 
                    "input": "$notifications", 
                    "as": "n", 
                    "in": { 
                        "$cond": [ 
                            { "$and": [ 
                                "$$n.enabled", 
                                    { "$setIsSubset": [ 
                                        [ "NG3" ], 
                                        "$$n.groups"
                                    ]}
                            ]}, 
                            1, 
                            0
                        ]
                    }
                }
            }
        }
    }}
])

在MongoDB 3.2之前,你需要采用一种效率较低的不同方法,因为它要求我们在$project阶段后$unwind我们的数组,因为$sum运算符不可用于从{3.0}向后的$project阶段。

从那里,您只需$group您的文档,并使用$sum运算符返回计数。

db.collection.aggregate([
    { "$match": { 
        "notifications.enabled": true, 
        "notifications.groups": "NG3" 
    }},
    { "$project": { 
        "count": { 
            "$map": { 
                "input": "$notifications", 
                "as": "n", 
                "in": { 
                    "$cond": [ 
                        { "$and": [ 
                            "$$n.enabled", 
                                { "$setIsSubset": [ 
                                    [ "NG3" ], 
                                    "$$n.groups"
                                ]}
                        ]}, 
                        1, 
                        0
                    ]
                }
            }
        }
    }},
    { "$unwind": "$count" }, 
    { "$group": { "_id": "$_id", "count": { "$sum": "$count" } } }
])

这两个查询产生类似于:

的内容
{ "_id" : ObjectId("57487d006a2fa1f11efc3208"), "count" : 1 }