嵌套数组的MongoDB投影

嵌套数组的MongoDB投影,mongodb,mongodb-query,aggregation-framework,Mongodb,Mongodb Query,Aggregation Framework,我有一个集合“accounts”,其中包含与此结构类似的文档: { "email" : "john.doe@acme.com", "groups" : [ { "name" : "group1", "contacts" : [ { "localId" : "c1", "address" : "some address 1" }, { "localId"

我有一个集合“accounts”,其中包含与此结构类似的文档:

{
    "email" : "john.doe@acme.com",
    "groups" : [
        {
            "name" : "group1",
            "contacts" : [
                { "localId" : "c1", "address" : "some address 1" },
                { "localId" : "c2", "address" : "some address 2" },
                { "localId" : "c3", "address" : "some address 3" }
            ]
        },
        {
            "name" : "group2",
            "contacts" : [
                { "localId" : "c1", "address" : "some address 1" },
                { "localId" : "c3", "address" : "some address 3" }
            ]
        }
    ]
}
通过

我将成功获得我感兴趣的特定帐户的组

问题:如何在指定“帐户”的某个“组”中获得有限的“联系人”列表?假设我有以下论点:

  • 帐户:电子邮件-“约翰。doe@acme.com"
  • 组:名称-“组1”
  • 联系人:本地化数组-[“c1”、“c3”、“不存在id”]
鉴于这些论点,我希望得到以下结果:

{
    "groups" : [
        {
            "name" : "group1", (might be omitted)
            "contacts" : [
                { "localId" : "c1", "address" : "some address 1" },
                { "localId" : "c3", "address" : "some address 3" }
            ]
        }
    ]
}
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "john.doe@acme.com", "groups" : { "name" : "group1", "contacts" : { "localId" : "c1", "address" : "some address 1" } } }
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "john.doe@acme.com", "groups" : { "name" : "group1", "contacts" : { "localId" : "c3", "address" : "some address 3" } } }
除了产生的联系人,我不需要其他任何东西

接近

为了简单起见,所有查询都尝试只获取一个匹配的联系人,而不是匹配联系人的列表。 我尝试了以下查询,但没有成功:

p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1", "contacts" : { $elemMatch: { "localId" : "c1" } } } } }
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1", "contacts.localId" : "c1" } } }
not working: returns whole array or nothing depending on localId


p = { "groups.$" : { $elemMatch: { "localId" : "c1" } } }
error: {
    "$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.",
    "code" : 17287
}


p = { "groups.contacts" : { $elemMatch: { "localId" : "c1" } } }
error: {
    "$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.",
    "code" : 17287
}
感谢您的帮助

您可以使用聚合框架的操作符。 例如:

db.contact.aggregate({$unwind:'$groups'}, {$unwind:'$groups.contacts'}, {$match:{email:'john.doe@acme.com', 'groups.name':'group1', 'groups.contacts.localId':{$in:['c1', 'c3', 'whatever']}}});
应给出以下结果:

{
    "groups" : [
        {
            "name" : "group1", (might be omitted)
            "contacts" : [
                { "localId" : "c1", "address" : "some address 1" },
                { "localId" : "c3", "address" : "some address 3" }
            ]
        }
    ]
}
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "john.doe@acme.com", "groups" : { "name" : "group1", "contacts" : { "localId" : "c1", "address" : "some address 1" } } }
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "john.doe@acme.com", "groups" : { "name" : "group1", "contacts" : { "localId" : "c3", "address" : "some address 3" } } }
如果只需要一个对象,则可以使用运算符。

2017更新 这样一个措词恰当的问题应该得到现代的回应。在现代MongoDB 3.2版中,请求的数组过滤实际上可以通过simply和pipeline阶段完成,这与原始的普通查询操作非常相似

db.accounts.aggregate([
{“$match”:{
“电子邮件”:“约翰。doe@acme.com",
“团体”:{
“$elemMatch”:{
“名称”:“组1”,
“contacts.localId:{“$in”:[“c1”,“c3”,null]}
}
}
}},
{“$addFields”:{
“团体”:{
“$filter”:{
“输入”:{
“$map”:{
“输入”:“$groups”,
“as”:“g”,
“在”:{
“名称”:“$$g.name”,
“联系人”:{
“$filter”:{
“输入”:“$$g.contacts”,
“as”:“c”,
“条件”:{
“$or”:[
{“$eq”:[“$$c.localId”,“c1”]},
{“$eq”:[“$$c.localId”,“c3”]}
]
} 
}
}
}
}
},
“as”:“g”,
“条件”:{
“$and”:[
{“$eq”:[“$$g.name”,“group1”]},
{“$gt”:[{“$size”:“$$g.contacts”},0]}
]
}
}
}
}}
])
这使得使用and运算符只返回符合条件的数组中的元素,并且性能远远优于使用and运算符。由于流水线级有效地反映了“查询”和“Project”的结构,从<代码> .FIN()/代码>操作中,这里的性能基本上与这样的操作一致。

请注意,如果目的是实际“跨文档”工作,将“多个”文档而不是“一个”文档中的详细信息汇集在一起,则通常需要某种类型的
$unwind
操作才能做到这一点,从而使数组项能够被“分组”访问


这基本上是一种方法:

db.accounts.aggregate([
//通过查询匹配文档
{“$match”:{
“电子邮件”:“约翰。doe@acme.com",
“groups.name”:“group1”,
“groups.contacts.localId:{“$in”:[“c1”,“c3”,null]},
}},
//反规范化嵌套数组
{“$unwind”:“$groups”},
{“$unwind”:“$groups.contacts”},
//根据需要过滤实际数组元素
{“$match”:{
“groups.name”:“group1”,
“groups.contacts.localId:{“$in”:[“c1”,“c3”,null]},
}},
//将中间结果分组。
{“$组”:{
“_id”:{“email”:“$email”,“name”:“$groups.name”},
“联系人”:{“$push”:“$groups.contacts”}
}},
//将最终结果分组
{“$组”:{
“\u id”:“$\u id.email”,
“组”:{“$push”:{
“名称”:“$\u id.name”,
“联系人”:“$contacts”
}}
}}
])
这是对多个匹配项的“数组筛选”,而
.find()
的基本投影功能无法做到这一点


您有“嵌套”数组,因此需要处理两次。与其他操作一起。

观众可以看到另一个操作。这就是你在这里“问”的方式。显示您尝试过的内容并给出具体错误。很好的提问方式。这最多应该是一个评论。实际上,您并没有解释如何得到结果。我只是对第一阶段匹配条件(“groups.contacts.localId”){“$in”:[“c1”,“c3”,null]}中的第三个参数感到好奇。这似乎适用于某个帐户中我的所有组,因此检查我不感兴趣的组联系人。由于我的商业案例,在给定的组中始终至少有一个匹配的联系人。基于这个条件,为了提高性能,我将省略第1阶段中的第3个参数。或者,我错了吗?@ CBopp,如果你考虑你所要求的标准,那么这是一个精确的表示。<>代码> NUL/<代码>匹配发生在该字段实际上不存在或实际上以某种形式包含单个值或数组的值时。99.999%的情况下,您不想要求进行
null
匹配,只需接受“提供的值”就是您所需要的。我强烈建议您遵循这一点,只使用不带
null
的不同值。好的,非常感谢。我将把你的答案标记为正确答案,因为它是按照预期的方式工作的,并且有一个很好的解释。在展开之前,其他人都没有匹配邮件,这可能会导致我的整个收藏被展开:'(@cbopp其他的回答有点错误。我试图让他们理解这一点,但有时人们就是不想听。不管怎样,很高兴你看到了前方的道路,并从中吸取了一些东西。这就是问题所在。@Neil Lunn你能用Java查询同样的问题吗,