聚合来自两个数组的 $sum 值

Aggregate $sum values from two arrays

我有这个collection

{
    "_id" : ObjectId("54f46f18c36dcc206d0cec38"),
    "project" : 23123,
    "title" : "Change of windows",
    "description": "Change to better windows on building A"
    "costs":[
      {
        category: 'Produktionskostnad',
        value: 3000
      },
      {
        category: 'Projekteringskostnad',
        value: 2000
      },
      {
        category: 'Overhead',
        value: 1000
      }
    ],
    "energySaving" : [ 
        {
            "energy" : "electricity",
            "type" : "lighting",
            "value" : 24324
        }, 
        {
            "energy" : "electricity",
            "type" : "equipment",
            "value" : 24324
        }, 
        {
            "energy" : "electricity",
            "type" : "fans",
            "value" : 24324
        }, 
        {
            "energy" : "electricity",
            "type" : "distribution",
            "value" : 24324
        }, 
        {
            "energy" : "electricity",
            "type" : "chiller",
            "value" : 24324
        }, 
        {
            "energy" : "electricity",
            "type" : "other",
            "value" : 24324
        }
    ]
}

我需要一个聚合来计算总成本和总节能。

为了节省开支,我有以下查询:

db.collection.aggregate( [
    { $unwind: "$energySaving" },
    { 
       $group: {
          _id: {
             title: '$title',
             description: '$description' 
          },
          totalEnergySaving: { $sum: '$energySaving.value' } 
       } 
    }
]);

但是如何计算同一查询中的总成本?我不能在同一查询中添加 $unwind 成本。我可以 "reset" $group 以某种方式再次查询吗?

TLDR;

在现代 MongoDB 版本中,我们只需简单地用 "double" 符号连续地为 "array sum" 和 "accumulator" 做一个 $group since we can pass the array items directly to $sum

db.collection.aggregate([
    { "$group": {
        "_id": {
            "title": "$title",
            "description": "$description"
        },
        "totalCosts": { "$sum": { "$sum": "$costs.value" } },
        "totalEnergySaving": { "$sum": { "$sum": "$energySaving.value" } }
     }}
 ])

2015 年原始答案

这需要一些技巧才能正确完成,但描述它的最佳方式是 "deal with grouping per document first" 然后 "group the totals later":

db.collection.aggregate([
    // Do cost per document
    { "$unwind": "$costs" },
    { "$group": {
        "_id": "$_id",
        "title": { "$first": "$title" },
        "description": { "$first": "$description" },
        "totalCosts": { "$sum": "$costs.value" },
        "energySaving": { "$first": "$energySaving" }
    }},

    // Do energy saving per document
    { "$unwind": "$energySaving" },
    { "$group": {
        "_id": "$_id",
        "title": { "$first": "$title" },
        "description": { "$first": "$description" },
        "totalCosts": { "$first": "$totalCosts" },
        "totalEnergySaving": { "$sum": "$energySaving.value" }
    }},

    // Now sum the real grouping
    { "$group": {
        "_id": {
            "title": "$title",
            "description": "$description"
        },
        "totalCosts": { "$sum": "$totalCosts" },
        "totalEnergySaving": { "$sum": "$totalEnergySaving" }
    }}
])

通过从数组值中计算出每个文档的奇异值,并通过展开和分组 "one array at a time" 来避免每个数组成员的项目复制,你形成了一个基础到你实际的奇异分组想要。

因此,当您 $unwind 一个数组时,您会得到文档的多个副本,每个数组成员现在在每个文档副本中表示为一个奇异值。你不想在这里做的是 $unwind 另一个数组,而你已经有一个未缠绕的数组,因为这将根据数组以相同方式拥有的成员数量创建尽可能多的 "more copies" 文档。

使用 $group back to the document _id value at this point ensures we are only working the the original parts of the document that was initially "un-wound". Normal grouping operators like $sum still apply, but $first 可以用来提取 "only one" 那些复制的字段值 "outside the array" 和几乎 return 文档到它的 "original form"对于您要保留的字段以及您有意从数组内容中聚合的任何内容。

对每个你想要的数组重复,然后转到另一个 $group 语句,这次用你之前创建的新奇异求和值一次加起来不止一个文档。

这就是在任何级别的分组中添加多个数组项的过程。当然,如果无论如何唯一的分组是在文档级别完成的,那么您可以在对每个数组进行分组后放弃,或者确实接受无论如何在客户端代码中进行可能更好。