【问题标题】:MongoDB nested query using aggregate function使用聚合函数的 MongoDB 嵌套查询
【发布时间】:2015-10-01 09:17:53
【问题描述】:

我有一个集合“superpack”,其中包含嵌套对象。示例文档如下所示。

{
  "_id" : ObjectId("56038c8cca689261baca93eb"),
  "name": "Test sub",
  "packs": [
  {
  "id": "55fbc7f6b0ce97a309b3cead",
  "name": "Classic",
  "packDispVal": "PACK",
  "billingPts": [
    {
      "id": "55fbc7f6b0ce97a309b3ceab",
      "name": "Classic 1 month",
      "expiryVal": 1,
      "amount": 20,
      "topUps": [
        {
          "id": "55fbc7f6b0ce97a309b3cea9",
          "name": "1 extra",
          "amount": 8
        },
        {
          "id": "55fbc7f6b0ce97a309b3ceaa",
          "name": "2 extra",
          "amount": 12
        }
      ]
    },
		{
      "id": "55fbc7f6b0ce97a309b3ceac",
      "name": "Classic 2 month",
      "expiryVal": 1,
      "amount": 30,
      "topUps": [
        {
          "id": "55fbc7f6b0ce97a309b3cea8",
          "name": "3 extra",
          "amount": 16
        }
      ]
    }
  ]
}
  ]
}

我需要使用 id 字段查询嵌套对象充值,结果应该只有选定的充值对象及其关联的父对象。当我在充值 id 55fbc7f6b0ce97a309b3cea9 上查询时,我希望输出如下所示。

{
  "_id" : ObjectId("56038c8cca689261baca93eb"),
  "name": "Test sub",
  "packs": [
    {
      "id": "55fbc7f6b0ce97a309b3cead",
      "name": "Classic",
      "packDispVal": "PACK",
      "billingPts": [
        {
          "id": "55fbc7f6b0ce97a309b3ceab",
          "name": "Classic 1 month",
          "expiryVal": 1,
          "amount": 20,
          "topUps": [
            {
              "id": "55fbc7f6b0ce97a309b3cea9",
              "name": "1 extra",
              "amount": 8
            }
          ]
        }
      ]
    }
  ]
}

我尝试了以下聚合查询。但是它没有返回任何结果。你能帮我看看,查询有什么问题吗?

db.superpack.aggregate( [{ $match: { "id": "55fbc7f6b0ce97a309b3cea9" } }, { $redact: {$cond: {   if: { $eq: [ "$id", "55fbc7f6b0ce97a309b3cea9" ]  },   "then": "$$KEEP",   else: "$$PRUNE" }}} ])

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    不幸的是,$redact 在这里不是一个可行的选择,因为递归$$DESCEND 基本上是在文档的所有级别上寻找一个名为“id”的字段。你不可能只在特定的嵌入级别上要求这样做,因为它要么全有,要么全无。

    这意味着您需要替代方法来过滤内容,而不是 $redact。所有“id”值都是唯一的,因此通过“set”操作过滤它们是没有问题的。

    所以最有效的方法是通过以下方式:

    db.docs.aggregate([
        { "$match": {
            "packs.billingPts.topUps.id": "55fbc7f6b0ce97a309b3cea9"
        }},
        { "$project": {
            "packs": {
                "$setDifference": [
                    { "$map": {
                        "input": "$packs",
                        "as": "pack",
                        "in": {
                            "$let": {
                                "vars": {
                                    "billingPts": {
                                        "$setDifference": [
                                            { "$map": {
                                                "input": "$$pack.billingPts",
                                                "as": "billing",
                                                "in": {
                                                    "$let": {
                                                        "vars": {
                                                            "topUps": {
                                                                "$setDifference": [
                                                                    { "$map": {
                                                                        "input": "$$billing.topUps",
                                                                        "as": "topUp",
                                                                        "in": {
                                                                            "$cond": [
                                                                                { "$eq": [ "$$topUp.id", "55fbc7f6b0ce97a309b3cea9" ] },
                                                                                "$$topUp",
                                                                                false
                                                                            ]
                                                                        }
                                                                    }},
                                                                    [false]
                                                                ]
                                                            }
                                                        },
                                                        "in": {
                                                            "$cond": [
                                                                { "$ne": [{ "$size": "$$topUps"}, 0] },
                                                                {
                                                                    "id": "$$billing.id",
                                                                    "name": "$$billing.name",
                                                                    "expiryVal": "$$billing.expiryVal",
                                                                    "amount": "$$billing.amount",
                                                                    "topUps": "$$topUps"
                                                                },
                                                                false
                                                            ]
                                                        }
                                                    }
                                                }
                                            }},
                                            [false]
                                        ]
                                    }
                                },
                                "in": {
                                    "$cond": [
                                        { "$ne": [{ "$size": "$$billingPts"}, 0 ] },
                                        { 
                                            "id": "$$pack.id",
                                            "name": "$$pack.name",
                                            "packDispVal": "$$pack.packDispVal",
                                            "billingPts": "$$billingPts"
                                        },
                                        false
                                    ]
                                }
                            }
                        }
                    }},
                    [false]
                ]
            }
        }}
    ])
    

    在深入到被过滤的最里面的数组之后,然后测试每个向外的结果数组的大小,看它是否为零,并从结果中省略它。

    这是一个很长的列表,但它是最有效的方法,因为每个数组都会首先在每个文档中进行过滤。

    一种不太有效的方法是使用$unwind$group 分离结果:

    db.docs.aggregate([
        { "$match": {
            "packs.billingPts.topUps.id": "55fbc7f6b0ce97a309b3cea9"
        }},
        { "$unwind": "$packs" },
        { "$unwind": "$packs.billingPts" },
        { "$unwind": "$packs.billingPts.topUps"},
        { "$match": {
            "packs.billingPts.topUps.id": "55fbc7f6b0ce97a309b3cea9"
        }},
        { "$group": {
            "_id": { 
                "_id": "$_id",
                "packs": {
                    "id": "$packs.id",
                    "name": "$packs.name",
                    "packDispVal": "$packs.packDispVal",
                    "billingPts": {
                        "id": "$packs.billingPts.id",
                        "name": "$packs.billingPts.name",
                        "expiryVal": "$packs.billingPts.expiryVal",
                        "amount": "$packs.billingPts.amount"
                    }
                }
            },
            "topUps": { "$push": "$packs.billingPts.topUps" }
        }},
        { "$group": {
            "_id": {
                "_id": "$_id._id",
                "packs": {
                    "id": "$_id.packs.id",
                    "name": "$_id.packs.name",
                    "packDispVal": "$_id.packs.packDispVal"
                }
            },
            "billingPts": { 
                "$push": {
                    "id": "$_id.packs.billingPts.id",
                    "name": "$_id.packs.billingPts.name",
                    "expiryVal": "$_id.packs.billingPts.expiryVal",
                    "amount": "$_id.packs.billingPts.amount",
                    "topUps": "$topUps"
                }
            }
        }},
        { "$group": {
            "_id": "$_id._id",
            "packs": {
                "$push": {
                    "id": "$_id.packs.id",
                    "name": "$_id.packs.name",
                    "packDispVal": "$_id.packs.packDispVal",
                    "billingPts": "$billingPts"
                }
            }
        }}
    ])
    

    列表看起来要简单得多,但当然$unwind 在这里引入了很多开销。分组返回的过程基本上是保留当前正在重构的数组级别之外的所有内容的副本,然后在下一阶段将该内容推回数组中,直到您回到根_id

    请注意,除非您打算这样的搜索匹配多个文档,或者如果您将通过有效减少非常大文档的响应大小而从减少网络流量中获得显着收益,那么建议您这些都不做,但遵循与第一个管道示例相同的设计,但在客户端代码中。

    虽然第一个示例在性能方面仍然可以,但发送到服务器仍然很麻烦,并且作为一般列表,通常在客户端代码中以更简洁的方式使用相同的操作编写,以处理和过滤结果结构。

    {
        "_id" : ObjectId("56038c8cca689261baca93eb"),
        "packs" : [
                {
                        "id" : "55fbc7f6b0ce97a309b3cead",
                        "name" : "Classic",
                        "packDispVal" : "PACK",
                        "billingPts" : [
                                {
                                        "id" : "55fbc7f6b0ce97a309b3ceab",
                                        "name" : "Classic 1 month",
                                        "expiryVal" : 1,
                                        "amount" : 20,
                                        "topUps" : [
                                                {
                                                        "id" : "55fbc7f6b0ce97a309b3cea9",
                                                        "name" : "1 extra",
                                                        "amount" : 8
                                                }
                                        ]
                                }
                        ]
                }
        ]
    }
    

    【讨论】:

    • 感谢您的解释。我是 MongoDB 的新手。如果我实现这一点,是否有任何性能影响,只需使用展开和匹配。我尝试使用以下查询。 db.doc.aggregate([{$unwind: "$packs"}, {$unwind: "$packs.billingPts"}, {$unwind:"$packs.billingPts.topUps"}, {$match: { "packs .billingPts.topUps.id": "55fbc7f6b0ce97a309b3cea8"}}])
    • @Girish 这里的部分解释是,使用$unwind 处理时可能会产生非常大的影响,因此首先显示内联数组的过滤。你真的应该尝试只使用$unwind,当你打算跨文档聚合时,而不仅仅是从匹配的文档中过滤内容,即使那样你真的应该先过滤。展开为每个数组成员创建文档的副本,因此这会成倍增加要处理的文档。同样总是 $match 首先,因为这会删除不符合条件的文档。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-03
    • 2018-09-10
    • 1970-01-01
    • 2021-08-13
    • 1970-01-01
    • 2021-10-06
    • 2017-07-26
    相关资源
    最近更新 更多