查询以在结果中过滤数组中的多个元素

时间:2014-11-03 15:12:47

标签: mongodb mongodb-query aggregation-framework

如果我想通过指定单个电子邮件ID来查询员工的属性,我确实有这个工作。

db.employee.find({},{
                _id: 0,
                employee: {
                    $elemMatch: {
                        email: "john@companyx.com"
                    }
                }})

假设我想通过指定多个电子邮件ID来查询以获取多个员工的属性。 我通读了,它与$或运算符有关,但我不知道如何将它放在那里..

我的mongoDB数据如下例所示:

{
 "_id" : ObjectId("53dbb05fa976627439d43884"),
 "employee" : [ 
  {
    "email" : "john@companyx.com",
    "deptName" : "x",

}, 
{
    "email" : "keen@companyx.com",
    "deptName" : "y",

},
{
    "email" : "hung@companyx.com",
    "deptName" : "y",

}
 ]
 }

3 个答案:

答案 0 :(得分:1)

阵列过滤只能由aggregation framework完成。与基本投影相比,它允许更多地处理文档。

就像任何查询一样,您应该首先使用$match管道,以便在可能的情况下使用索引。无论之后正在执行什么其他操作:

db.employee.aggregate([

    //  Always match first to reduce results
    { "$match": {
         "employee.email": { "$in": ["john@companyx.com", "keen@companyx.com"] }
    }},

    // Unwind to de-normalize the array elements as documents
    { "$unwind": "$employee" },

    // Match to "filter" the array content
    { "$match": {
         "employee.email": { "$in": ["john@companyx.com", "keen@companyx.com"] }
    }},

    // Group back to a document with the array
    { "$group": {
        "_id": "$_id",
        "employee": { "$push": "$employee" }
    }},

    // Optionally project to remove the "_id" field from results
    { "$project": {
        "_id": 0,
        "employee": 1
    }}
])

这就解释了基本过程。找到"文件"根据您的条件,您使用$unwind有效地使数组的每个元素成为其自己的文档,共享任何父字段。额外的$match是"过滤"结果中的那些元素。当$group完成后,只有匹配的元素被放回到数组中。

使用MongoDB 2.6,您可以采用不同的方式执行此操作,以便更好地使用更大的阵列。有一些新的运算符,例如$map,用于处理数组" in-line"不使用$unwind。还有其他" set"过滤选项为$setDifference。所以你可以这样做,你的文件总是包含唯一的"电子邮件"他们自己的数组中的值:

db.employee.aggregate([

    //  Always match first to reduce results
    { "$match": {
         "employee.email": { "$in": ["john@companyx.com", "keen@companyx.com"] }
    }},

    // Project filtered array content "in-line"
    { "$project": {
        "_id": 0,
        "employee": {
            "$setDifference": [
                { "$map": {
                    "input": "$employee",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$or": [
                                { "$eq": [ "$$el.email", "john@companyx.com" ] },
                                { "$eq": [ "$$el.email", "keen@companyx.com" ] }
                            ]},
                            "$$el",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])

除了前面提到的新运算符之外,此处使用$cond运算符来评估通过$map传递的数组的每个元素,以查看它是否满足条件。如果是,则在结果数组中返回元素,否则元素为false

然后$setDifference运算符"过滤"来自" set"的任何false值返回,因为任何重复都是如此,因此数组元素在每个文档中都需要是唯一的。

对于"非独特的"在现代版本中,第一种方法总是存在这种替代方式:

db.employee.aggregate([

    //  Always match first to reduce results
    { "$match": {
         "employee.email": { "$in": ["john@companyx.com", "keen@companyx.com"] }
    }},

    // Redact removes document levels that do not match the condition
    { "$redact": {
        "$cond": [
            { "$or": [
                { "$eq": [ 
                    { "$ifNull": [ "$email", "john@companyx.com" ] },
                    "john@companyx.com"
                ]},
                { "$eq": [ 
                    { "$ifNull": [ "$email", "keen@companyx.com" ] },
                    "keen@companyx.com"
                ]}
            ]},
            "$$DESCEND",
            "$$PRUNE"
        ]
    }}
])

这使用$redact以一种轻微的方式从文档中删除与条件不匹配的数组元素。这里的问题是$redact是递归的,所以这就是为什么我们测试测试字段的存在以及它不存在的地方只返回一个值来匹配。实际上实际上只需要一个$ifNull语句。

基本上,无论你选择什么方法,aggregation framework都有"加强"操纵比基本投影更多的文件。

答案 1 :(得分:0)

如果我理解了你的意思,我认为你的解决方案是$in运算符,我认为你应该把你的条件放在选择器参数中,而不是在投影中,如果我有了解你的需求。所以你的查询将是这样的:

db.employee.find({email:{$in:["email1@foo.bar", "email2@bar.foo"]}},{_id:0})

答案 2 :(得分:0)

投影操作符 $ elemMatch 只返回数组中的一个元素,所以我认为你可以考虑聚合,比如

var emails = [ "john@companyx.com", "hung@companyx.com" ];
var match = {
    $match : {
        "employee.email" : {
            $in : emails
        }
    }
};

db.employee.aggregate([ match, {
    $unwind : "$employee"
}, match, {
    $group : {
        _id : "$_id",
        employee : {
            $push : "$employee"
        }
    }
}, {
    $project : {
        _id : 0,
        employee : 1
    }
} ]);