MongoDB聚合查询中,使用$lookup只包含(或投影)一些字段返回查询结果。

70
在Mongo中,进行$lookup聚合后,我希望请求仅返回一些字段而不是整个文档。
我有以下查询:
db.somecollection.aggregate([{
    $lookup: {
        from: "campaigns",
        localField: "campId",
        foreignField: "_id",
        as: "campaign"
    }
}, {
    $unwind: "$campaign"
}, {
    $lookup: {
        from: "entities",
        localField: "campaign.clientid",
        foreignField: "_id",
        as: "campaign.client"
    }
}]);

这个请求将会返回给我这个:

{
"_id" : ObjectId("56cc7cd1cc2cf62803ebfdc7"),
"campId" : ObjectId("56c740e4479f46e402efda84"),
"articleId" : ObjectId("56c742c06094640103ba3843"),
"campaign" : {
    "_id" : ObjectId("56c740e4479f46e402efda84"),
    "clientid" : ObjectId("56c740b8479f46e402efda83"),
    "client" : [
        {
            "_id" : ObjectId("56c740b8479f46e402efda83"),
            "username" : "someusername",
            "shhh" : "somehashedpassword",
            "email" : "mail@mail.com",
        }
    ]
}
请求已经正常工作,但我希望过滤campaign.client字段,只获取例如_idusername。在MongoDB aggregate请求中有一种方法可以实现这个吗?

17
使用$project阶段。{ $project : { _id1 : 1, campId : 1, articleId : 1, campaign._id : 1, campaign.clientid : 1, campaign.client._id : 1, campaign.client.username : 1 } }注:此处为MongoDB中的聚合管道操作,$project阶段用于投影(即只保留)指定文档字段,如示例代码所示。 - SiddAjmera
1
@SiddharthAjmera 你应该将它转换为答案,你刚刚救了我的一天! - Rafael Beckel
5个回答

106

为了帮助他人,@SiddhartAjmera的回答是正确的,我只需要为"campaign.clientid"这样的嵌套值添加双引号。

最终代码应为:

db.somecollection.aggregate([
      {
        "$lookup": {
          "from": "campaigns",
          "localField": "campId",
          "foreignField": "_id",
          "as": "campaign"
        }
      },
      {
        "$unwind": "$campaign"
      },
      {
        "$lookup": {
          "from": "entities",
          "localField": "campaign.clientid",
          "foreignField": "_id",
          "as": "campaign.client"
        }
      },
      {
        "$project": {
          "_id": 1,
          "campId": 1,
          "articleId": 1,
          "campaign._id": 1,
          "campaign.clientid": 1,
          "campaign.client._id": 1,
          "campaign.client.username": 1
        }
      }
]);

4
为什么我们要使用$unwind?为什么它是必需的? - Faisal
20
如果您不使用$unwind,它会将结果返回为一个数组形式的活动列表。 - Yuriy Chachora
1
嘿...有没有一种方法可以从父级返回所有内容(在项目中),仅从子集返回特定字段。 (我使用“”作为指示符)例如:$project:{          “”:1,          “campaign._id”:1,          “campaign.clientid”:1,          “campaign.client._id”:1,          “campaign.client.username”:1 } - MLissCetrus
@MLissCetrus,你找到方法了吗? - AlpeshVasani
@MLissCetrus AlpeshVasani,你找到解决的方法了吗? - Arif Fathurrohman
使用 $project 在结尾可能会很繁琐,需要提及许多字段来选择/取消选择。相反,最好在 $lookup 中使用 pipeline,并在 pipeline 中编写 $project。 - Saurav Ghimire

27

$lookup中使用pipeline$project

db.somecollection.aggregate([{
    $lookup: {
        from: "campaigns",
        localField: "campId",
        foreignField: "_id",
        as: "campaign"
    }
}, {
    $unwind: "$campaign"
}, {
    $lookup: {
        from: "entities",
        let: { client_id: "$campaign.clientid" },    
        pipeline : [
            { $match: { $expr: { $eq: [ "$_id", "$$client_id" ] } }, },
            { $project : { _id:1, username:1 } }
        ],
        as: "campaign.client"
    }
}]);

5

补充一下之前的回答:如果你想忽略某个项目,可以将它的值设为0,其余的会被检索到,这样就不需要在所有列表项上都写1了:

db.somecollection.aggregate([
  {
    "$lookup": {
      "from": "campaigns",
      "localField": "campId",
      "foreignField": "_id",
      "as": "campaign"
    }
  },
  {
    "$unwind": "$campaign"
  },
  {
    "$lookup": {
      "from": "entities",
      "localField": "campaign.clientid",
      "foreignField": "_id",
      "as": "campaign.client"
    }
  },
  {
    "$project": {
      "campaign.client.shhh": 0
    }
  }
])

4

我知道回答这个问题的时间很晚。但在我看来,更新有时可能会非常有益。
项目阶段很棒,但您仍然需要在$lookup阶段请求整个码头。字段仅在其后的投影阶段中进行过滤。

在MongoDB 3.6发布之后,您可以向$lookup阶段添加管道,以指定多个连接条件。在他们的官方文档中查找更多详细信息。
使用$lookup指定多个连接条件

您可以按以下方式转换聚合管道,以获得所需的结果:

db.somecollection.aggregate([{
    $lookup: {
        from: "campaigns",
        localField: "campId",
        foreignField: "_id",
        as: "campaign"
    }
}, {
    $unwind: "$campaign"
}, {
    $lookup: {
        from: "entities",
        let: {clientid: '$campaign.clientid'},
        pipeline: [
           { '$match': 
             { '$expr': 
               { 
                  '$eq': ['$_id', '$$clientid']   
               }
             }
           },
           { '$project': 
              '_id': 1,
              'username': 1
           }
        ]
        as: "campaign.client"
    }
}]);

通过这种方式,您可以在$lookup阶段内部过滤连接的集合字段。
请注意,内部管道的$match阶段中使用了$$符号。它用于表示在let块中定义的自定义字段。


0
尝试了几种方法后,我找到了这种解决问题的方式,它看起来更简洁易读。
const query = [
  { $match: match },
  {
    $lookup: {
      'from': 'accounts',
      'localField': 'course_account_owner_id',
      'foreignField': '_id',
      'as': 'accountTmp'
    }
  },
  { $unwind: { path: '$accountTmp', preserveNullAndEmptyArrays: true } },
  {
    $addFields: {
      'account.account_name': '$accountTmp.account_name',
      'account.account_logo': '$accountTmp.account_logo',
    }
  }, {
    $project: {
        'accountTmp': 0
    }
  }
];

这是因为$project阶段的特定行为,即使在排除不必要的字段时,仍然会将所有主要字段保留在输出中,但一旦您指定了至少一个要包含的子文档字段,它立即关闭所有主要字段。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接