按条件匹配的嵌入数组元素的数量查找文档

时间:2015-11-13 18:08:38

标签: mongodb mongodb-query aggregation-framework

我是MongoDB的新手,在完成任务时需要帮助:

我正在使用MongoDB来查询一个人所采取的行动。这些操作嵌入在人员文档中,如下所示:

{
    "_id" : ObjectId("56447ac0583d4871570041c3"),
    "email" : "email@example.net",
    "actions" : [ 
        {
            "name" : "support",
            "created_at" : ISODate("2015-10-17T01:40:35.000Z"),
        }, 
        {
            "name" : "hide",
            "created_at" : ISODate("2015-10-16T01:40:35.000Z")
        },
        {
            "name" : "support",
            "created_at" : ISODate("2015-10-17T03:40:35.000Z"),
        }
    ]
}

一个人可以使用不同的操作名称执行多项操作(supporthide仅为2个示例。)

我知道我可以找到所有至少有support次行动的人:

db.test.find({'actions.name':'support'})

我想做的是,检索所有至少有X support次行动的人。这可能不使用JavaScript语法吗?由于人们可能有数百个行动,这将是缓慢的。

所以,如果我希望所有人至少有2 support个动作,我知道的唯一方法是使用js语法:

db.test.find({$where: function() {
  return this.actions.filter(function(action){
    return action.name = 'support';
  }).length >= 2;
}});

此查询是否还有其他/更好/更快的可能性?

2 个答案:

答案 0 :(得分:2)

最好的方法是使用.aggregate()方法来访问聚合管道。

您可以使用$match运算符缩小要在管道上处理的文档的大小,以过滤掉与给定条件不匹配的所有文档。

您需要使用$redact运算符仅返回名称为"支持"的元素数量的文档。在您的数组中$gte 2$map运算符返回一个与您的critera匹配的子文档数组,并返回false,您可以使用$setDifference运算符轻松删除它们。当然$size运算符返回数组的大小。

db.test.aggregate([
    { "$match": { 
        "actions.name": "support", 
        "actions.2": { "$exists": true } 
    }},
    { "$redact": {
        "$cond": [
            { "$gte": [
                { "$size": { 
                    "$setDifference": [
                        { "$map": { 
                            "input": "$actions", 
                            "as": "action", 
                            "in": { 
                                "$cond": [
                                    { "$eq": [ "$$action.name", "support" ] }, 
                                    "$$action", 
                                    false
                                ]
                            }
                        }}, 
                        [false]
                     ]
                }}, 
                2
            ]}, 
            "$$KEEP", 
            "$$PRUNE"
        ]
    }}
])

从MongoDB 3.2可以使用$filter运算符来处理。

db.test.aggregate([
    { "$match": { 
        "actions.name": "support", 
        "actions.2": { "$exists": true } 
    }},
    { "$redact": {
        "$cond": [
            { "$gte": [
                { "$size": { 
                    "$filter": { 
                        "input": "$actions", 
                        "as": "action", 
                        "cond": { "$eq": [ "$$action.name", "support" ] }
                    }
                }}, 
                2
            ]}, 
            "$$KEEP", 
            "$$PRUNE"
        ]
    }}
])

@BlakesSeven指出:

  只要被过滤的数据是"唯一的",

$setDifference就可以了。在这种情况下,它应该""没问题,但是如果任何两个结果包含相同的日期,那么通过将两个结果视为一个结果会使结果偏斜。 $filter是更好的选择,但如果数据不是唯一的,那么现在就需要放松。

答案 1 :(得分:-2)

我没有根据您的尝试对此进行基准测试,但这听起来像是Mongo聚合框架的一个很好的用例。

db.test.aggregate([
{$unwind: "$actions"},
{$group: { 
  _id: { _id: "$_id", action: "$actions},
  count: {$sum: 1}
},
{$match: {$and: [{count: {$gt: 2}}, {"_id.action": "support"]}
]);

请注意,我没有在mongo中运行它,因此它可能有一些语法问题。

背后的想法是:

  1. unwind actions数组,使数组的每个元素成为自己的文档
  2. group由_id - 动作类型对生成的集合,并计算每个集合的数量。
  3. match将仅过滤我们感兴趣的内容。