从$ lookup中获取数组中元素的过滤计数以及整个文档

时间:2017-07-03 14:38:58

标签: mongodb mongodb-query aggregation-framework

我在MongoDB中有这个查询:

db.emailGroup.aggregate([
    {
        "$lookup": 
        {
            "from": "link",
            "localField": "_id",
            "foreignField": "emailGroupId",
            "as": "link"
        },
    },
    {
        "$unwind": "$link"
    },
    {
        "$match": {
             'link.originalLink': ""
        }
    },
    {
        "$group" : {
            _id: '$_id',
            link: {
                $push: '$link'
            }
        }
    },
    {
        "$project": { 
            "size": { 
                "$sum": { 
                    "$map": { 
                        "input": "$link", 
                        "as": "l", 
                        "in": { 
                            "$size": {
                                "$ifNull": [
                                    "$$l.linkHistory", []
                                ]
                            }
                        } 
                    } 
                } 
            }
        }
    }
])

EmailGroup有partId字段。我使用$ lookup来“加入”其他集合来汇总她的字段。我需要group by partId字段并为partId组求和自定义字段“size”。这可能吗?额外问题:如何将emailGroup字段添加到查询结果中?

示例文件:

emailGroup:

{
    "_id" : ObjectId("594a6c47f51e075db713ccb6"),
    "partId" : "f56c7c71eb14a20e6129a667872f9c4f",
}

链接:

{
    "_id" : ObjectId("594b96d6f51e075db67c44c9"),
    "originalLink" : "",
    "emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
    "linkHistory" : [ 
        {
            "_id" : ObjectId("594b96f5f51e075db713ccdf"),
        }, 
        {
            "_id" : ObjectId("594b971bf51e075db67c44ca"),
        }
    ]
}

1 个答案:

答案 0 :(得分:4)

寻找者的注释 - 外国人数

比最初回答的要好一点,就是实际使用MongoDB 3.6中较新的$lookup形式。这实际上可以在“子管道”表达式中进行“计数”,而不是返回“数组”以进行后续过滤和计数,甚至使用$unwind

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "let": { "id": "$_id" },
    "pipeline": [
      { "$match": {
        "originalLink": "",
        "$expr": { "$eq": [ "$$id", "$_id" ] }
      }},
      { "$count": "count" }
    ],
    "as": "linkCount"    
  }},
  { "$addFields": {
    "linkCount": { "$sum": "$linkCount.count" }
  }}
])

不是原始问题所要求的,而是现在最优化形式的以下答案的一部分,当然$lookup的结果被缩减为“匹配计数”而不是“所有匹配的文件”。

原始

执行此操作的正确方法是将"linkCount"添加到$group阶段,并在父文档的任何其他字段上添加$first,以便获取“在作为$unwind的结果的数组上处理$lookup之前,单数“形式为状态”:

所有细节

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$unwind": "$link" },
  { "$match": { "link.originalLink": "" } },
  { "$group": {
    "_id": "$_id",
    "partId": { "$first": "$partId" },
    "link": { "$push": "$link" },
    "linkCount": {
      "$sum": {
        "$size": {
          "$ifNull": [ "$link.linkHistory", [] ]
        } 
      }   
    }
  }}
])

产地:

{
    "_id" : ObjectId("594a6c47f51e075db713ccb6"),
    "partId" : "f56c7c71eb14a20e6129a667872f9c4f",
    "link" : [ 
        {
            "_id" : ObjectId("594b96d6f51e075db67c44c9"),
            "originalLink" : "",
            "emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
            "linkHistory" : [ 
                {
                    "_id" : ObjectId("594b96f5f51e075db713ccdf")
                }, 
                {
                    "_id" : ObjectId("594b971bf51e075db67c44ca")
                }
            ]
        }
    ],
    "linkCount" : 2
}

按PartId分组

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$unwind": "$link" },
  { "$match": { "link.originalLink": "" } },
  { "$group": {
    "_id": "$partId",
    "linkCount": {
      "$sum": {
        "$size": {
          "$ifNull": [ "$link.linkHistory", [] ]
        } 
      }   
    }
  }}
])

可生产

{
    "_id" : "f56c7c71eb14a20e6129a667872f9c4f",
    "linkCount" : 2
}

使用$unwind然后$match这样做的原因是MongoDB在按此顺序发出时实际处理管道的原因。这就是$lookup所发生的事情,正如操作的"explain"输出所示:

    {
        "$lookup" : {
            "from" : "link",
            "as" : "link",
            "localField" : "_id",
            "foreignField" : "emailGroupId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "originalLink" : {
                    "$eq" : ""
                }
            }
        }
    }, 
    {
        "$group" : {

我将在该输出中留下$group的部分,以证明其他两个管道阶段“消失”。这是因为它们已经“汇总”到$lookup管道阶段,如图所示。事实上,MongoDB如何处理将$lookup的结果“加入”父文档数组的结果可能超出BSON限制的可能性。

您可以交替编写如下操作:

所有细节

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$addFields": {
    "link": {
      "$filter": {
        "input": "$link",
        "as": "l",
        "cond": { "$eq": [ "$$l.originalLink", "" ] }    
      }
    },
    "linkCount": {
      "$sum": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$link",
              "as": "l",
              "cond": { "$eq": [ "$$l.originalLink", "" ] }
            }
          },
          "as": "l",
          "in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
        }     
      }
    }    
  }}
])

按分区ID分组

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$addFields": {
    "link": {
      "$filter": {
        "input": "$link",
        "as": "l",
        "cond": { "$eq": [ "$$l.originalLink", "" ] }    
      }
    },
    "linkCount": {
      "$sum": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$link",
              "as": "l",
              "cond": { "$eq": [ "$$l.originalLink", "" ] }
            }
          },
          "as": "l",
          "in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
        }     
      }
    }    
  }},
  { "$unwind": "$link" },
  { "$group": {
    "_id": "$partId",
    "linkCount": { "$sum": "$linkCount" } 
  }}
])

具有相同的输出但与第一个查询“不同”,因为$filter此处已应用于“{strong>所有结果$lookup返回到父文档的新数组。

因此,在性能方面,以第一种方式实现它以及在“过滤之前”可移植到可能的大型结果集实际上更有效,否则会破坏16MB BSON限制。

  

注意:如果您没有$addFields可用(与MongoDB 3.4一起添加),请使用$project并指定您想要返回的“所有”字段。

作为感兴趣的人的旁注,在MongoDB的未来版本(可能是3.6及更高版本)中,您可以使用$replaceRoot而不是$addFields来使用新版{{1}管道运营商。这样做的好处是作为“块”,我们可以通过$let$mergeObjects内容声明为变量,这意味着您不需要编写相同的$filter“两次”:

"filtered"

尽管如此,使用$lookup然后$unwind模式进行此类“过滤”$match操作的最佳方法仍然是“仍然”,直到您可以提供查询参数为止直接到$lookup

  

注意:通过“直接”在这里我并不意味着 $lookup的“非相关”形式,它也应该出现在MongoDB 3.6版本中,因为这确实会发出另一个父集合中每个文档的“管道”执行。因此,该功能仍然无法取代目前仅检索匹配项目的“最有效”方式。