【问题标题】:MongoDB nested lookup with 3 levels具有 3 个级别的 MongoDB 嵌套查找
【发布时间】:2016-07-01 09:22:24
【问题描述】:

我需要从数据库中检索整个单个对象层次结构作为 JSON。实际上,将高度赞赏有关实现此结果的任何其他解决方案的建议。我决定使用带有 $lookup 支持的 MongoDB。

所以我有三个集合:

派对

{ "_id" : "2", "name" : "party2" }
{ "_id" : "5", "name" : "party5" }
{ "_id" : "4", "name" : "party4" }
{ "_id" : "1", "name" : "party1" }
{ "_id" : "3", "name" : "party3" }    

地址

{ "_id" : "a3", "street" : "Address3", "party_id" : "2" }
{ "_id" : "a6", "street" : "Address6", "party_id" : "5" }
{ "_id" : "a1", "street" : "Address1", "party_id" : "1" }
{ "_id" : "a5", "street" : "Address5", "party_id" : "5" }
{ "_id" : "a2", "street" : "Address2", "party_id" : "1" }
{ "_id" : "a4", "street" : "Address4", "party_id" : "3" }

地址评论

{ "_id" : "ac2", "address_id" : "a1", "comment" : "Comment2" }
{ "_id" : "ac1", "address_id" : "a1", "comment" : "Comment1" }
{ "_id" : "ac5", "address_id" : "a5", "comment" : "Comment6" }
{ "_id" : "ac4", "address_id" : "a3", "comment" : "Comment4" }
{ "_id" : "ac3", "address_id" : "a2", "comment" : "Comment3" }

我需要检索所有具有所有相应地址的各方,并将地址 cmet 作为记录的一部分。我的聚合:

db.party.aggregate([{
    $lookup: {
        from: "address",
        localField: "_id",
        foreignField: "party_id",
        as: "address"
    }
},
{
    $unwind: "$address"
},
{
    $lookup: {
        from: "addressComment",
        localField: "address._id",
        foreignField: "address_id",
        as: "address.addressComment"
    }
}])

结果很奇怪。有些记录没问题。但是缺少_id: 4 的派对(没有地址)。另外,结果集中有两个Party_id: 1(但地址不同):

{
    "_id": "1",
    "name": "party1",
    "address": {
        "_id": "2",
        "street": "Address2",
        "party_id": "1",
        "addressComment": [{
            "_id": "3",
            "address_id": "2",
            "comment": "Comment3"
        }]
    }
}{
    "_id": "1",
    "name": "party1",
    "address": {
        "_id": "1",
        "street": "Address1",
        "party_id": "1",
        "addressComment": [{
            "_id": "1",
            "address_id": "1",
            "comment": "Comment1"
        },
        {
            "_id": "2",
            "address_id": "1",
            "comment": "Comment2"
        }]
    }
}{
    "_id": "3",
    "name": "party3",
    "address": {
        "_id": "4",
        "street": "Address4",
        "party_id": "3",
        "addressComment": []
    }
}{
    "_id": "5",
    "name": "party5",
    "address": {
        "_id": "5",
        "street": "Address5",
        "party_id": "5",
        "addressComment": [{
            "_id": "5",
            "address_id": "5",
            "comment": "Comment5"
        }]
    }
}{
    "_id": "2",
    "name": "party2",
    "address": {
        "_id": "3",
        "street": "Address3",
        "party_id": "2",
        "addressComment": [{
            "_id": "4",
            "address_id": "3",
            "comment": "Comment4"
        }]
    }
}

请帮我解决这个问题。我对 MongoDB 还很陌生,但我觉得它可以满足我的需求。

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework lookup


    【解决方案1】:

    “麻烦”的原因是第二个聚合阶段 - { $unwind: "$address" }。它删除了_id: 4 的记录(因为它的地址数组是空的,正如你提到的)并为_id: 1_id: 5 的当事人生成两条记录(因为他们每个人都有两个地址)。

    • 为防止删除没有地址的各方,您应将$unwind 阶段的preserveNullAndEmptyArrays 选项设置为true

    • 为了防止各方重复其不同的地址,您应该将$group 聚合阶段添加到您的管道。另外,使用$project 阶段和$filter 运算符来排除输出中的空地址记录。

    db.party.aggregate([{
      $lookup: {
        from: "address",
        localField: "_id",
        foreignField: "party_id",
        as: "address"
      }
    }, {
      $unwind: {
        path: "$address",
        preserveNullAndEmptyArrays: true
      }
    }, {
      $lookup: {
        from: "addressComment",
        localField: "address._id",
        foreignField: "address_id",
        as: "address.addressComment",
      }
    }, {
      $group: {
        _id : "$_id",
        name: { $first: "$name" },
        address: { $push: "$address" }
      }
    }, {
      $project: {
        _id: 1,
        name: 1,
        address: {
          $filter: { input: "$address", as: "a", cond: { $ifNull: ["$$a._id", false] } }
        } 
      }
    }]);
    

    【讨论】:

    • 坦克你鲥鱼!但是记录 4 有一个小问题:{ "_id": "4", "name": "party4", "address": [{ "addressComment": [] }] } 如您所见 - 地址应该为空,但它是一个空记录...如果地址注释为空,我们可以跳过地址吗?在其他情况下,此地址将被视为记录。
    • 实际上,根据 $unwind 操作的新“preserveNullAndEmptyArrays”字段的描述(自 3.2 起),我看到提供的解决方案按预期工作。现在我们可以跳过“$project”这一步并指定这个“$unwind”而不是简单的一个:$unwind: {path: "$address",preserveNullAndEmptyArrays: true}。我会接受你的回答,感谢你快速而清晰的回复!
    • @Shad 我有非常相似的问题。这里 OP 的代码在party 集合中只有一个名为name 的属性,因此您使用$first$group 中获取它。假设我有 10 多个属性,那么有什么方法可以自动获取所有属性而不单独提及每个属性?
    • 如果同一地址 id 有不同的 cmets,我们如何为“addressComment”添加组子句?如前所述,此查询适用于“地址”级别的分组。
    【解决方案2】:

    使用 mongodb 3.6 及以上$lookup 语法,无需使用$unwind 即可连接嵌套字段非常简单。

    db.party.aggregate([
      { "$lookup": {
        "from": "address",
        "let": { "partyId": "$_id" },
        "pipeline": [
          { "$match": { "$expr": { "$eq": ["$party_id", "$$partyId"] }}},
          { "$lookup": {
            "from": "addressComment",
            "let": { "addressId": "$_id" },
            "pipeline": [
              { "$match": { "$expr": { "$eq": ["$address_id", "$$addressId"] }}}
            ],
            "as": "address"
          }}
        ],
        "as": "address"
      }},
      { "$unwind": "$address" }
    ])
    

    【讨论】:

    猜你喜欢
    • 2017-12-15
    • 2021-08-06
    • 2018-12-22
    • 2012-02-21
    • 2017-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多