MongoDB使用聚合函数嵌套查询

时间:2015-09-30 14:14:02

标签: mongodb mongodb-query aggregation-framework

我有一个集合" 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字段查询嵌套对象topup,结果应该只有选定的topup对象及其关联的父对象。当我在topup 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" }}} ])

1 个答案:

答案 0 :(得分:2)

不幸的是$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
                                            }
                                    ]
                            }
                    ]
            }
    ]
}